Repository: CSCfi/rems
Pull request #1070 (2019-04-05 12:47:37)

Accept licenses command
Applicant and member is treated the same and can use the new accept-licenses command for accepting all the terms of use. The previous checkbox solution is removed.

Member attributes are shown if there are any (e.g. Malice in test users)

See https://github.com/CSCfi/rems/issues/653


Patch:
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index c02381962..579eb5a45 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -49,6 +49,7 @@
   :actions {:accept-invitation "Accept invitation"
             :accept-invitation-success [:div [:p "Joining the application was successful."] [:p "Next you will be taken to the application."]]
             :accept-invitation-already-member [:div [:p "You are already a member."] [:p "Next you will be taken to the application."]]
+            :accept-licenses "Accept terms of use"
             :add-member "Add member"
             :applicant "Applicant"
             :application "Application"
@@ -144,6 +145,7 @@
                           :decided "Decided"
                           :decision-requested "Decision requested"
                           :draft-saved "Saved"
+                          :licenses-accepted "Accepted terms of use"
                           :member-added "Member added"
                           :member-invited "Member invited"
                           :member-joined "Member joined"
@@ -256,8 +258,10 @@
          :event "Event"
          :events "Events"
          :failed "Failed"
+         :has-accepted-licenses "You have accepted the terms of use."
          :licenses "Terms of Use"
          :maxlength "(max %1 characters)"
+         :must-accept-licenses "To be eligible to the resources of this application, each member must accept the following terms of use."
          :optional "(optional)"
          :resources "Resources"
          :save "Save as draft"
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index c05fb5308..e9f068065 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -49,6 +49,7 @@
   :actions {:accept-invitation "Hyväksy kutsu"
             :accept-invitation-success [:div [:p "Liittyminen hakemukseen onnistui."] [:p "Seuraavaksi pääset katsomaan hakemuksen tietoja."]]
             :accept-invitation-already-member [:div. [:p "Olet jo jäsen."] [:p "Seuraavaksi pääset katsomaan hakemuksen tietoja."]]
+            :accept-licenses "Hyväksy käyttöehdot"
             :add-member "Lisää jäsen"
             :applicant "Hakija"
             :application "Hakemus"
@@ -144,6 +145,7 @@
                           :decided "Päätetty"
                           :decision-requested "Pyydetty päätöstä"
                           :draft-saved "Tallennettu"
+                          :licenses-accepted "Käyttöehdot hyväksytty"
                           :member-added "Lisätty jäsen"
                           :member-invited "Kutsuttu jäsen"
                           :member-joined "Jäsen liittyi"
@@ -256,8 +258,10 @@
          :event "Tapahtuma"
          :events "Tapahtumat"
          :failed "Epäonnistui"
+         :has-accepted-licenses "Olet hyväksynyt käyttöehdot."
          :licenses "Käyttöehdot"
          :maxlength "(enintään %1 merkkiä)"
+         :must-accept-licenses "Saadakseen oikeudet haettuihin resursseihin, jokaisen hakijan pitää hyväksyä seuraavat käyttöehdot."
          :optional "(ei pakollinen)"
          :resources "Resurssit"
          :save "Tallenna luonnos"
diff --git a/src/clj/rems/api/applications.clj b/src/clj/rems/api/applications.clj
index 4d5d20fc6..7d4848af2 100644
--- a/src/clj/rems/api/applications.clj
+++ b/src/clj/rems/api/applications.clj
@@ -184,6 +184,8 @@
       :return AcceptInvitationResult
       (ok (applications/accept-invitation (getx-user-id) invitation-token)))
 
+    (command-endpoint :application.command/accept-invitation commands/AcceptInvitationCommand)
+    (command-endpoint :application.command/accept-licenses commands/AcceptLicensesCommand)
     (command-endpoint :application.command/add-member commands/AddMemberCommand)
     (command-endpoint :application.command/invite-member commands/InviteMemberCommand)
     (command-endpoint :application.command/approve commands/ApproveCommand)
diff --git a/src/clj/rems/api/schema.clj b/src/clj/rems/api/schema.clj
index 81ffd9736..99720937b 100644
--- a/src/clj/rems/api/schema.clj
+++ b/src/clj/rems/api/schema.clj
@@ -95,7 +95,6 @@
 
 (s/defschema V2License
   {:license/id s/Int
-   :license/accepted s/Bool
    :license/type (s/enum :text :link :attachment)
    :license/title LocalizedString
    (s/optional-key :license/link) LocalizedString
@@ -134,7 +133,8 @@
    :application/last-activity DateTime
    :application/applicant s/Str
    :application/applicant-attributes {s/Keyword s/Str}
-   :application/members #{{:userid s/Str}}
+   :application/members #{{:userid s/Str
+                           s/Keyword s/Str}}
    :application/invited-members #{{:name s/Str
                                    :email s/Str}}
    :application/resources [V2Resource]
diff --git a/src/clj/rems/application/commands.clj b/src/clj/rems/application/commands.clj
index 440ef148c..9342ed6ce 100644
--- a/src/clj/rems/application/commands.clj
+++ b/src/clj/rems/application/commands.clj
@@ -16,8 +16,11 @@
 
 (s/defschema SaveDraftCommand
   (assoc CommandBase
-         :field-values {s/Num s/Str}
-         :accepted-licenses #{s/Num}))
+         :field-values {s/Num s/Str}))
+
+(s/defschema AcceptLicensesCommand
+  (assoc CommandBase
+         :accepted-licenses s/Any))
 
 (s/defschema SubmitCommand
   CommandBase)
@@ -82,9 +85,9 @@
          :comment s/Str))
 
 (def command-schemas
-  {#_:application.command/accept-license
-   #_:application.command/require-license
+  {#_:application.command/require-license
    :application.command/accept-invitation AcceptInvitationCommand
+   :application.command/accept-licenses AcceptLicensesCommand
    :application.command/add-member AddMemberCommand
    :application.command/invite-member InviteMemberCommand
    :application.command/approve ApproveCommand
diff --git a/src/clj/rems/application/events.clj b/src/clj/rems/application/events.clj
index c56ad7e96..7792daf44 100644
--- a/src/clj/rems/application/events.clj
+++ b/src/clj/rems/application/events.clj
@@ -63,7 +63,10 @@
 (s/defschema DraftSavedEvent
   (assoc EventBase
          :event/type (s/enum :application.event/draft-saved)
-         :application/field-values {s/Int s/Str}
+         :application/field-values {s/Int s/Str}))
+(s/defschema LicensesAcceptedEvent
+  (assoc EventBase
+         :event/type (s/enum :application.event/licenses-accepted)
          :application/accepted-licenses #{s/Int}))
 (s/defschema MemberAddedEvent
   (assoc EventBase
@@ -111,6 +114,7 @@
    :application.event/decided DecidedEvent
    :application.event/decision-requested DecisionRequestedEvent
    :application.event/draft-saved DraftSavedEvent
+   :application.event/licenses-accepted LicensesAcceptedEvent
    :application.event/member-added MemberAddedEvent
    :application.event/member-invited MemberInvitedEvent
    :application.event/member-joined MemberJoinedEvent
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index d19cfd8dc..cdc48dc65 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -21,8 +21,9 @@
                                               :application.command/submit
                                               :application.command/remove-member
                                               :application.command/invite-member
-                                              :application.command/uninvite-member]
-                                  :member []
+                                              :application.command/uninvite-member
+                                              :application.command/accept-licenses]
+                                  :member [:application.command/accept-licenses]
                                   :handler [:see-everything
                                             :application.command/remove-member
                                             :application.command/uninvite-member]
@@ -33,7 +34,9 @@
                                   :everyone-else [:application.command/accept-invitation]})
 
 (def ^:private submitted-permissions {:applicant [:application.command/remove-member
-                                                  :application.command/uninvite-member]
+                                                  :application.command/uninvite-member
+                                                  :application.command/accept-licenses]
+                                      :member [:application.command/accept-licenses]
                                       :handler [:see-everything
                                                 :application.command/add-member
                                                 :application.command/remove-member
@@ -50,6 +53,7 @@
                                                 :application.command/decide]})
 
 (def ^:private closed-permissions {:applicant []
+                                   :member []
                                    :handler [:see-everything]
                                    :commenter [:see-everything]
                                    :decider [:see-everything]
@@ -161,10 +165,9 @@
                                             :resource/ext-id (:resource/ext-id resource)})
                                          (:application/resources event))
              :application/licenses (map (fn [license]
-                                          {:license/id (:license/id license)
-                                           :license/accepted false})
+                                          {:license/id (:license/id license)})
                                         (:application/licenses event))
-             :application/accepted-licenses {(:event/actor event) #{}}
+             :application/accepted-licenses {"applicant" #{}}
              :application/events []
              :application/form {:form/id (:form/id event)}
              :application/workflow {:workflow/id (:workflow/id event)
@@ -175,17 +178,16 @@
                                     :workflow.dynamic/awaiting-commenters #{}
                                     :workflow.dynamic/awaiting-deciders #{}})))
 
-(defn- set-accepted-licences [licenses accepted-licenses]
-  (map (fn [license]
-         (assoc license :license/accepted (contains? accepted-licenses (:license/id license))))
-       licenses))
-
 (defmethod event-type-specific-application-view :application.event/draft-saved
   [application event]
   (-> application
       (assoc :application/modified (:event/time event))
-      (assoc ::draft-answers (:application/field-values event))
-      (update :application/licenses set-accepted-licences (:application/accepted-licenses event))
+      (assoc ::draft-answers (:application/field-values event))))
+
+(defmethod event-type-specific-application-view :application.event/licenses-accepted
+  [application event]
+  (-> application
+      (assoc :application/modified (:event/time event))
       (assoc-in [:application/accepted-licenses (:event/actor event)] (:application/accepted-licenses event))))
 
 (defmethod event-type-specific-application-view :application.event/member-invited
@@ -435,6 +437,17 @@
                            (sort-by :license/id))]
     (merge-lists-by :license/id rich-licenses app-licenses)))
 
+(defn enrich-user-attributes [application get-user]
+  (letfn [(enrich-members [members]
+            (->> members
+                 (map (fn [member]
+                        (merge member
+                               (get-user (:userid member)))))
+                 set))]
+    (update application
+            :application/members
+            enrich-members)))
+
 (defn enrich-with-injections [application {:keys [get-form get-catalogue-item get-license get-user]}]
   (let [answer-versions (remove nil? [(::draft-answers application)
                                       (::submitted-answers application)
@@ -456,7 +469,8 @@
         set-application-description
         (update :application/resources enrich-resources get-catalogue-item)
         (update :application/licenses enrich-licenses get-license)
-        (assoc :application/applicant-attributes (get-user (:application/applicant application))))))
+        (assoc :application/applicant-attributes (get-user (:application/applicant application)))
+        (enrich-user-attributes get-user))))
 
 (defn build-application-view [events injections]
   (-> (reduce application-view nil events)
diff --git a/src/clj/rems/css/styles.clj b/src/clj/rems/css/styles.clj
index 458024ac8..f8d1934e5 100644
--- a/src/clj/rems/css/styles.clj
+++ b/src/clj/rems/css/styles.clj
@@ -565,8 +565,8 @@
                       :white-space "pre-wrap"}]
     [:.license-title {;; hax for opening misalignment
                       :margin-top (u/px 3)
-                      :line-height (u/rem 1.25)
-                      :font-size (u/rem 1.25)}]]
+                      :line-height (u/rem 1)
+                      :font-size (u/rem 1)}]]
    [:.collapsing {:-webkit-transition "height 0.1s linear"
                   :-o-transition "height 0.1s linear"
                   :transition "height 0.1s linear"}]
diff --git a/src/clj/rems/migrations/convert_to_dynamic_applications.clj b/src/clj/rems/migrations/convert_to_dynamic_applications.clj
index c19c62612..192bbe610 100644
--- a/src/clj/rems/migrations/convert_to_dynamic_applications.clj
+++ b/src/clj/rems/migrations/convert_to_dynamic_applications.clj
@@ -39,7 +39,11 @@
                                       :application/field-values (->> (:items form)
                                                                      (map (fn [item]
                                                                             [(:id item) (:value item)]))
-                                                                     (into {}))
+                                                                     (into {}))})
+    (applications/add-dynamic-event! {:event/type :application.event/licenses-accepted
+                                      :event/time (:start application)
+                                      :event/actor (:applicantuserid application)
+                                      :application/id (:id application)
                                       :application/accepted-licenses (->> (:licenses form)
                                                                           (filter :approved)
                                                                           (map :id)
diff --git a/src/clj/rems/workflow/dynamic.clj b/src/clj/rems/workflow/dynamic.clj
index 2209705b2..2cf3c06cf 100644
--- a/src/clj/rems/workflow/dynamic.clj
+++ b/src/clj/rems/workflow/dynamic.clj
@@ -72,21 +72,10 @@
   [user-ids injections]
   (apply merge-with into (keep #(invalid-user-error % injections) user-ids)))
 
-(defn- validate-licenses [application]
-  (let [all-licenses (set (map :license/id (:application/licenses application)))
-        user-id (:application/applicant application)
-        accepted-licenses (get-in application [:application/accepted-licenses user-id])
-        missing-licenses (set/difference all-licenses accepted-licenses)]
-    (->> (sort missing-licenses)
-         (map (fn [license-id]
-                {:type :t.form.validation/required
-                 :license-id license-id})))))
-
 (defn- validation-error [application {:keys [validate-form-answers]}]
   (let [form-id (:form/id application)
         answers (:rems.application.model/draft-answers application)
-        errors (concat (validate-form-answers form-id {:items answers})
-                       (validate-licenses application))]
+        errors (validate-form-answers form-id {:items answers})]
     (when (seq errors)
       {:errors errors})))
 
@@ -112,7 +101,11 @@
 (defmethod command-handler :application.command/save-draft
   [cmd _application _injections]
   (ok {:event/type :application.event/draft-saved
-       :application/field-values (:field-values cmd)
+       :application/field-values (:field-values cmd)}))
+
+(defmethod command-handler :application.command/accept-licenses
+  [cmd _application _injections]
+  (ok {:event/type :application.event/licenses-accepted
        :application/accepted-licenses (set (:accepted-licenses cmd))}))
 
 (defmethod command-handler :application.command/submit
@@ -249,7 +242,7 @@
     result))
 
 (defn handle-command [cmd application injections]
-  (commands/validate-command cmd) ;; this is here mostly for tests, commands via the api are validated by compojure-api
+  (commands/validate-command cmd) ; this is here mostly for tests, commands via the api are validated by compojure-api
   (let [permissions (permissions/user-permissions application (:actor cmd))]
     (if (contains? permissions (:type cmd))
       (-> (command-handler cmd application injections)
@@ -265,7 +258,6 @@
         command {:application-id 123 :time (DateTime. 1000)
                  :type :application.command/save-draft
                  :field-values {}
-                 :accepted-licenses #{}
                  :actor "applicant"}]
     (testing "executes command when user is authorized"
       (is (:success (handle-command command application {}))))
diff --git a/src/cljc/rems/text.cljc b/src/cljc/rems/text.cljc
index 3f2355ec4..8f1ca4b9d 100644
--- a/src/cljc/rems/text.cljc
+++ b/src/cljc/rems/text.cljc
@@ -68,6 +68,7 @@
    :application.event/decided :t.applications.events/decided
    :application.event/decision-requested :t.applications.events/decision-requested
    :application.event/draft-saved :t.applications.events/draft-saved
+   :application.event/licenses-accepted :t.applications.events/licenses-accepted
    :application.event/member-added :t.applications.events/member-added
    :application.event/member-invited :t.applications.events/member-invited
    :application.event/member-joined :t.applications.events/member-joined
diff --git a/src/cljs/rems/actions/accept_licenses.cljs b/src/cljs/rems/actions/accept_licenses.cljs
new file mode 100644
index 000000000..92db4c84b
--- /dev/null
+++ b/src/cljs/rems/actions/accept_licenses.cljs
@@ -0,0 +1,25 @@
+(ns rems.actions.accept-licenses
+  (:require [re-frame.core :as rf]
+            [rems.actions.action :refer [action-button action-form-view action-comment button-wrapper]]
+            [rems.status-modal :as status-modal]
+            [rems.text :refer [text]]
+            [rems.util :refer [post!]]))
+
+(rf/reg-event-fx
+ ::send-accept-licenses
+ (fn [_ [_ {:keys [application-id licenses on-finished]}]]
+   (status-modal/common-pending-handler! (text :t.actions/accept-licenses))
+   (post! "/api/applications/accept-licenses"
+          {:params {:application-id application-id
+                    :accepted-licenses licenses}
+           :handler (partial status-modal/common-success-handler! on-finished)
+           :error-handler status-modal/common-error-handler!})
+   {}))
+
+(defn accept-licenses-action-button [application-id licenses on-finished]
+  [button-wrapper {:id "accept-licenses-button"
+                   :text (text :t.actions/accept-licenses)
+                   :class "btn-primary"
+                   :on-click #(rf/dispatch [::send-accept-licenses {:application-id application-id
+                                                                    :licenses licenses
+                                                                    :on-finished on-finished}])}])
diff --git a/src/cljs/rems/application.cljs b/src/cljs/rems/application.cljs
index d009531f0..d3e7c083b 100644
--- a/src/cljs/rems/application.cljs
+++ b/src/cljs/rems/application.cljs
@@ -2,6 +2,7 @@
   (:require [clojure.string :as str]
             [medley.core :refer [map-vals]]
             [re-frame.core :as rf]
+            [rems.actions.accept-licenses :refer [accept-licenses-action-button]]
             [rems.actions.action :refer [action-button action-form-view action-comment action-collapse-id button-wrapper]]
             [rems.actions.add-member :refer [add-member-action-button add-member-form]]
             [rems.actions.approve-reject :refer [approve-reject-action-button approve-reject-form]]
@@ -32,7 +33,7 @@
   (.setTimeout js/window #(.scrollTo js/window 0 0) 500)) ;; wait until faded out
 
 (defn reload! [application-id]
-  (rf/dispatch [:rems.application/enter-application-page application-id]))
+  (rf/dispatch [:rems.application/reload-application-page application-id]))
 
 (defn- in-processing? [application]
   (not (contains? #{:application.state/approved
@@ -40,6 +41,12 @@
                     :application.state/closed}
                   (:application/state application))))
 
+(defn- accepted-licenses? [application userid]
+  (every? (or (get (:application/accepted-licenses application)
+                   userid)
+              #{})
+          (map :license/id (:application/licenses application))))
+
 (defn- disabled-items-warning [application]
   (when (in-processing? application)
     (when-some [resources (->> (:application/resources application)
@@ -70,20 +77,14 @@
 (defn- format-validation-errors
   [application errors]
   (let [fields-by-id (->> (get-in application [:application/form :form/fields])
-                          (index-by [:field/id]))
-        licenses-by-id (->> (get-in application [:application/licenses])
-                            (index-by [:license/id]))]
+                          (index-by [:field/id]))]
     [:div (text :t.form.validation/errors)
      (into [:ul]
            (concat
             (for [{:keys [type field-id]} errors
                   :when field-id]
               (let [field (get fields-by-id field-id)]
-                (format-validation-error type (:field/title field))))
-            (for [{:keys [type license-id]} errors
-                  :when license-id]
-              (let [license (get licenses-by-id license-id)]
-                (format-validation-error type (:license/title license))))))]))
+                (format-validation-error type (:field/title field))))))]))
 
 
 ;;;; State
@@ -112,9 +113,24 @@
                                                  (map (juxt :field/id :field/value))
                                                  (into {}))
                               :show-diff {}
-                              :validation-errors nil
-                              :accepted-licenses (set (get (:application/accepted-licenses application)
-                                                           (:application/applicant application)))})))
+                              :validation-errors nil})))
+
+(rf/reg-event-fx
+ ::reload-application-page
+ (fn [{:keys [db]} [_ id]]
+   {::reload-application id}))
+
+(rf/reg-fx
+ ::reload-application
+ (fn [id]
+   (fetch (str "/api/applications/" id)
+          {:handler #(rf/dispatch [::reload-application-result %])})))
+
+(rf/reg-event-db
+ ::reload-application-result
+ (fn [db [_ application]]
+   (assoc db
+          ::application application)))
 
 (rf/reg-event-db
  ::set-validation-errors
@@ -122,12 +138,11 @@
    (prn errors)
    (assoc-in db [::edit-application :validation-errors] errors)))
 
-(defn- save-application! [description application-id field-values accepted-licenses]
+(defn- save-application! [description application-id field-values]
   (status-modal/common-pending-handler! description)
   (post! "/api/applications/save-draft"
          {:params {:application-id application-id
-                   :field-values field-values
-                   :accepted-licenses accepted-licenses}
+                   :field-values field-values}
           :handler (partial status-modal/common-success-handler! #(rf/dispatch [::enter-application-page application-id]))
           :error-handler status-modal/common-error-handler!}))
 
@@ -138,17 +153,15 @@
          edit-application (::edit-application db)]
      (save-application! description
                         (:application/id application)
-                        (:field-values edit-application)
-                        (:accepted-licenses edit-application)))
+                        (:field-values edit-application)))
    {:db (assoc-in db [::edit-application :validation-errors] nil)}))
 
-(defn- submit-application! [application description application-id field-values accepted-licenses]
+(defn- submit-application! [application description application-id field-values]
   ;; TODO: deduplicate with save-application!
   (status-modal/common-pending-handler! description)
   (post! "/api/applications/save-draft"
          {:params {:application-id application-id
-                   :field-values field-values
-                   :accepted-licenses accepted-licenses}
+                   :field-values field-values}
           :handler (fn [response]
                      (if (:success response)
                        (post! "/api/applications/submit"
@@ -172,8 +185,7 @@
      (submit-application! application
                           description
                           (:application/id application)
-                          (:field-values edit-application)
-                          (:accepted-licenses edit-application)))
+                          (:field-values edit-application)))
    {:db (assoc-in db [::edit-application :validation-errors] nil)}))
 
 (defn- save-attachment [{:keys [db]} [_ field-id file description]]
@@ -233,13 +245,6 @@
  (fn [db [_ field-id]]
    (update-in db [::edit-application :show-diff field-id] not)))
 
-(rf/reg-event-db
- ::set-license-accepted
- (fn [db [_ license-id accepted]]
-   (update-in db [::edit-application :accepted-licenses] (if accepted
-                                                           #(conj % license-id)
-                                                           #(disj % license-id)))))
-
 (defn- id-to-name [id]
   (str "field" id))
 
@@ -359,7 +364,7 @@
                            :placeholder placeholder
                            :max-length max-length
                            :class (when validation "is-invalid")
-                           :value value
+                           :defaultValue value
                            :on-change (set-field-value id)}]]))
 
 (defn- texta-field
@@ -374,7 +379,7 @@
                 :placeholder placeholder
                 :max-length max-length
                 :class (if validation "form-control is-invalid" "form-control")
-                :value value
+                :defaultValue value
                 :on-change (set-field-value id)}]]))
 
 ;; TODO: custom :diff-component, for example link to both old and new attachment
@@ -507,38 +512,21 @@
     [:div.form-group
      [:label (localized title)]]))
 
-(defn- set-license-accepted
-  [id]
-  (fn [event]
-    (rf/dispatch [::set-license-accepted id (.. event -target -checked)])))
-
-(defn- license [id title approved readonly validation content]
-  [:div.license
-   [field-validation-message validation title]
-   [:div.form-check
-    [:input.form-check-input {:type "checkbox"
-                              :name (str "license" id)
-                              :disabled readonly
-                              :class (when validation "is-invalid")
-                              :checked (boolean approved)
-                              :on-change (set-license-accepted id)}]
-    [:span.form-check-label content]]])
-
 (defn- link-license
-  [{:keys [accepted readonly validation] :as opts}]
+  [{:keys [accepted readonly] :as opts}]
   (let [id (:license/id opts)
         title (localized (:license/title opts))
         link (localized (:license/link opts))]
-    [license id title accepted readonly validation
+    [:div.license
      [:a.license-title {:href link :target "_blank"}
       title " " (external-link)]]))
 
 (defn- text-license
-  [{:keys [accepted readonly validation] :as opts}]
+  [{:keys [accepted readonly] :as opts}]
   (let [id (:license/id opts)
         title (localized (:license/title opts))
         text (localized (:license/text opts))]
-    [license id title accepted readonly validation
+    [:div.license
      [:div.license-panel
       [:span.license-title
        [:a.license-header.collapsed {:data-toggle "collapse"
@@ -602,23 +590,30 @@
                              :readonly readonly?
                              :app-id (:application/id application))]))]}]))
 
-(defn- application-licenses [application edit-application]
+(defn- application-licenses [application edit-application userid]
   (when-let [licenses (not-empty (:application/licenses application))]
-    (let [accepted-licenses (:accepted-licenses edit-application)
-          license-validations (index-by [:license-id] (:validation-errors edit-application))
+    (let [application-id (:application/id application)
+          accepted-licenses (get (:application/accepted-licenses application) userid)
+          possible-commands (:application/permissions application)
           form-fields-editable? (form-fields-editable? application)
           readonly? (not form-fields-editable?)]
       [collapsible/component
        {:id "form"
         :title (text :t.form/licenses)
         :always
-        [:div.form-group.field
+        [:div
+         [:p (text :t.form/must-accept-licenses)]
          (into [:div#licenses]
                (for [license licenses]
                  [license-field (assoc license
                                        :accepted (contains? accepted-licenses (:license/id license))
-                                       :readonly readonly?
-                                       :validation (license-validations (:license/id license)))]))]}])))
+                                       :readonly readonly?)]))
+         (if (accepted-licenses? application userid)
+           (text :t.form/has-accepted-licenses)
+           (when (contains? possible-commands :application.command/accept-licenses)
+             [:div.commands
+              ;; TODO consider saving the form first so that no data is lost for the applicant
+              [accept-licenses-action-button application-id (mapv :license/id licenses) #(reload! application-id)]]))]}])))
 
 
 (defn- format-event [event]
@@ -775,17 +770,15 @@
                            :group? (or (seq members)
                                        (seq invited-members))
                            :can-remove? false
-                           :accepted-licenses? (every? (or (get (:application/accepted-licenses application)
-                                                                (:application/applicant application))
-                                                           #{})
-                                                       (map :license/id (:application/licenses application)))}]]
+                           :accepted-licenses? (accepted-licenses? application (:userid applicant))}]]
             (concat
              (for [member members]
                [member-info {:element-id id
                              :attributes member
                              :application application
                              :group? true
-                             :can-remove? can-remove?}])
+                             :can-remove? can-remove?
+                             :accepted-licenses? (accepted-licenses? application (:userid member))}])
              (for [invited-member invited-members]
                [member-info {:element-id id
                              :attributes invited-member
@@ -845,7 +838,7 @@
                      ^{:key (:resource/id resource)}
                      [:li (localized (:catalogue-item/title resource))]))]}])
 
-(defn- render-application [application edit-application]
+(defn- render-application [application edit-application userid]
   (let [messages (remove nil?
                          [(disabled-items-warning application) ; NB: eval this here so we get nil or a warning
                           (when-let [errors (:validation-errors edit-application)]
@@ -860,7 +853,7 @@
      [:div.mt-3 [applicants-info application]]
      [:div.mt-3 [applied-resources application]]
      [:div.my-3 [application-fields application edit-application]]
-     [:div.my-3 [application-licenses application edit-application]]
+     [:div.my-3 [application-licenses application edit-application userid]]
      [:div.mb-3 [actions-form application]]]))
 
 ;;;; Entrypoint
@@ -868,12 +861,13 @@
 (defn application-page []
   (let [application @(rf/subscribe [::application])
         edit-application @(rf/subscribe [::edit-application])
+        userid (get-in @(rf/subscribe [:identity]) [:user :eppn])
         loading? (not application)]
     (if loading?
       [:div
        [:h2 (text :t.applications/application)]
        [spinner/big]]
-      [render-application application edit-application])))
+      [render-application application edit-application userid])))
 
 
 ;;;; Guide


Test patch:
diff --git a/src/clj/rems/db/test_data.clj b/src/clj/rems/db/test_data.clj
index 91b5ffc89..2b199e6de 100644
--- a/src/clj/rems/db/test_data.clj
+++ b/src/clj/rems/db/test_data.clj
@@ -29,7 +29,7 @@
 (def +fake-user-data+
   {"developer" {:eppn "developer" :mail "developer@example.com" :commonName "Developer"}
    "alice" {:eppn "alice" :mail "alice@example.com" :commonName "Alice Applicant"}
-   "malice" {:eppn "malice" :mail "malice@example.com" :commonName "Malice Applicant"}
+   "malice" {:eppn "malice" :mail "malice@example.com" :commonName "Malice Applicant" :twinOf "alice" :other "Attribute Value"}
    "bob" {:eppn "bob" :mail "bob@example.com" :commonName "Bob Approver"}
    "carl" {:eppn "carl" :mail "carl@example.com" :commonName "Carl Reviewer"}
    "elsa" {:eppn "elsa" :mail "elsa@example.com" :commonName "Elsa Roleless"}
@@ -469,8 +469,7 @@
                                   :actor user-id
                                   :application-id app-id
                                   :time (get-in form [:application :start])
-                                  :field-values {}
-                                  :accepted-licenses #{}})]
+                                  :field-values {}})]
     (when dynamic-workflow?
       (applications/add-application-created-event! {:application-id app-id
                                                     :catalogue-item-ids catids
@@ -489,10 +488,7 @@
                                   :round 0
                                   :licid license-id
                                   :actoruserid user-id
-                                  :state "approved"})
-      (swap! save-draft-command
-             update :accepted-licenses
-             conj license-id))
+                                  :state "approved"}))
     (when dynamic-workflow?
       (let [error (applications/command! @save-draft-command)]
         (assert (nil? error) error)))
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index e7856fef0..2aa7ce60c 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -181,7 +181,8 @@
                 "application.event/submitted"]
                (map :event/type (get application :application/events))))
         (is (= #{"application.command/remove-member"
-                 "application.command/uninvite-member"}
+                 "application.command/uninvite-member"
+                 "application.command/accept-licenses"}
                (set (get application :application/permissions))))))
 
     (testing "getting dynamic application as handler"
@@ -301,9 +302,7 @@
               :errors [{:type "forbidden"}]}
              (send-command "bob" {:type :application.command/save-draft
                                   :application-id application-id
-                                  :field-values {}
-                                  :accepted-licenses #{}}))))
-
+                                  :field-values {}}))))
     (testing "submitting"
       (is (= {:success true}
              (send-command user-id {:type :application.command/submit
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index c0d8237f4..005fd0522 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -176,7 +176,6 @@
                                                            :catalogue-item/expired false
                                                            :catalogue-item/archived false}]
                                   :application/licenses [{:license/id 30
-                                                          :license/accepted false
                                                           :license/type :link
                                                           :license/title {:en "en title"
                                                                           :fi "fi title"
@@ -190,7 +189,6 @@
                                                           :license/enabled true
                                                           :license/archived false}
                                                          {:license/id 31
-                                                          :license/accepted false
                                                           :license/type :text
                                                           :license/title {:en "en title"
                                                                           :fi "fi title"
@@ -204,7 +202,6 @@
                                                           :license/enabled true
                                                           :license/archived false}
                                                          {:license/id 32
-                                                          :license/accepted false
                                                           :license/type :attachment
                                                           :license/title {:en "en title"
                                                                           :fi "fi title"
@@ -254,276 +251,283 @@
                                      :event/actor "applicant"
                                      :application/id 1
                                      :application/field-values {41 "foo"
-                                                                42 "bar"}
-                                     :application/accepted-licenses #{30 31 32}})
+                                                                42 "bar"}})
                 expected-application (deep-merge expected-application
                                                  {:application/modified (DateTime. 2000)
                                                   :application/last-activity (DateTime. 2000)
                                                   :application/events events
-                                                  :application/licenses [{:license/accepted true}
-                                                                         {:license/accepted true}
-                                                                         {:license/accepted true}]
                                                   :application/description "foo"
-                                                  :application/accepted-licenses {"applicant" #{30 31 32}}
+                                                  :application/accepted-licenses {"applicant" #{}}
                                                   :application/form {:form/fields [{:field/value "foo"}
                                                                                    {:field/value "bar"}]}})]
             (is (= expected-application (apply-events events)))
 
-            (testing "> submitted"
-              (let [events (conj events {:event/type :application.event/submitted
-                                         :event/time (DateTime. 3000)
+            (testing "> accepted licenses"
+              (let [events (conj events {:event/type :application.event/licenses-accepted
+                                         :event/time (DateTime. 2500)
                                          :event/actor "applicant"
-                                         :application/id 1})
-                    expected-application (merge expected-application
-                                                {:application/last-activity (DateTime. 3000)
-                                                 :application/events events
-                                                 :application/state :application.state/submitted})]
-                (is (= expected-application (apply-events events)))
+                                         :application/id 1
+                                         :application/accepted-licenses #{30 31 32}})
+                    expected-application (deep-merge expected-application
+                                                     {:application/modified (DateTime. 2500)
+                                                      :application/last-activity (DateTime. 2500)
+                                                      :application/events events
+                                                      :application/accepted-licenses {"applicant" #{30 31 32}}})]
+                (is (= expected-application (apply-events events))))
 
-                (testing "> returned"
-                  (let [events (conj events {:event/type :application.event/returned
-                                             :event/time (DateTime. 4000)
-                                             :event/actor "handler"
-                                             :application/id 1
-                                             :application/comment "fix stuff"})
-                        expected-application (deep-merge expected-application
-                                                         {:application/last-activity (DateTime. 4000)
-                                                          :application/events events
-                                                          :application/state :application.state/returned
-                                                          :application/form {:form/fields [{:field/previous-value "foo"}
-                                                                                           {:field/previous-value "bar"}]}})]
-                    (is (= expected-application (apply-events events)))
+              (testing "> submitted"
+                (let [events (conj events {:event/type :application.event/submitted
+                                           :event/time (DateTime. 3000)
+                                           :event/actor "applicant"
+                                           :application/id 1})
+                      expected-application (merge expected-application
+                                                  {:application/last-activity (DateTime. 3000)
+                                                   :application/events events
+                                                   :application/state :application.state/submitted})]
+                  (is (= expected-application (apply-events events)))
 
-                    (testing "> draft saved x2"
-                      (let [events (conj events
-                                         {:event/type :application.event/draft-saved
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "applicant"
-                                          :application/id 1
-                                          ;; non-submitted versions should not show up as the previous value
-                                          :application/field-values {41 "intermediate draft"
-                                                                     42 "intermediate draft"}
-                                          :application/accepted-licenses #{30 31 32}}
-                                         {:event/type :application.event/draft-saved
-                                          :event/time (DateTime. 6000)
-                                          :event/actor "applicant"
-                                          :application/id 1
-                                          :application/field-values {41 "new foo"
-                                                                     42 "new bar"}
-                                          :application/accepted-licenses #{30 31 32}})
-                            expected-application (deep-merge expected-application
-                                                             {:application/modified (DateTime. 6000)
-                                                              :application/last-activity (DateTime. 6000)
-                                                              :application/events events
-                                                              :application/description "new foo"
-                                                              :application/form {:form/fields [{:field/value "new foo"
-                                                                                                :field/previous-value "foo"}
-                                                                                               {:field/value "new bar"
-                                                                                                :field/previous-value "bar"}]}})]
-                        (is (= expected-application (apply-events events)))
+                  (testing "> returned"
+                    (let [events (conj events {:event/type :application.event/returned
+                                               :event/time (DateTime. 4000)
+                                               :event/actor "handler"
+                                               :application/id 1
+                                               :application/comment "fix stuff"})
+                          expected-application (deep-merge expected-application
+                                                           {:application/last-activity (DateTime. 4000)
+                                                            :application/events events
+                                                            :application/state :application.state/returned
+                                                            :application/form {:form/fields [{:field/previous-value "foo"}
+                                                                                             {:field/previous-value "bar"}]}})]
+                      (is (= expected-application (apply-events events)))
 
-                        (testing "> submitted"
-                          (let [events (conj events
-                                             {:event/type :application.event/submitted
-                                              :event/time (DateTime. 7000)
-                                              :event/actor "applicant"
-                                              :application/id 1})
-                                expected-application (merge expected-application
-                                                            {:application/last-activity (DateTime. 7000)
-                                                             :application/events events
-                                                             :application/state :application.state/submitted})]
-                            (is (= expected-application (apply-events events)))))))
+                      (testing "> draft saved x2"
+                        (let [events (conj events
+                                           {:event/type :application.event/draft-saved
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "applicant"
+                                            :application/id 1
+                                            ;; non-submitted versions should not show up as the previous value
+                                            :application/field-values {41 "intermediate draft"
+                                                                       42 "intermediate draft"}}
+                                           {:event/type :application.event/draft-saved
+                                            :event/time (DateTime. 6000)
+                                            :event/actor "applicant"
+                                            :application/id 1
+                                            :application/field-values {41 "new foo"
+                                                                       42 "new bar"}})
+                              expected-application (deep-merge expected-application
+                                                               {:application/modified (DateTime. 6000)
+                                                                :application/last-activity (DateTime. 6000)
+                                                                :application/events events
+                                                                :application/description "new foo"
+                                                                :application/form {:form/fields [{:field/value "new foo"
+                                                                                                  :field/previous-value "foo"}
+                                                                                                 {:field/value "new bar"
+                                                                                                  :field/previous-value "bar"}]}})]
+                          (is (= expected-application (apply-events events)))
 
-                    (testing "> submitted (no draft saved)"
-                      (let [events (conj events
-                                         {:event/type :application.event/submitted
-                                          :event/time (DateTime. 7000)
-                                          :event/actor "applicant"
-                                          :application/id 1})
-                            expected-application (deep-merge expected-application
-                                                             {:application/last-activity (DateTime. 7000)
-                                                              :application/events events
-                                                              :application/state :application.state/submitted
-                                                              ;; when there was no draft-saved event, the current and
-                                                              ;; previous submitted answers must be the same
-                                                              :application/form {:form/fields [{:field/value "foo"
-                                                                                                :field/previous-value "foo"}
-                                                                                               {:field/value "bar"
-                                                                                                :field/previous-value "bar"}]}})]
-                        (is (= expected-application (apply-events events)))))))
+                          (testing "> submitted"
+                            (let [events (conj events
+                                               {:event/type :application.event/submitted
+                                                :event/time (DateTime. 7000)
+                                                :event/actor "applicant"
+                                                :application/id 1})
+                                  expected-application (merge expected-application
+                                                              {:application/last-activity (DateTime. 7000)
+                                                               :application/events events
+                                                               :application/state :application.state/submitted})]
+                              (is (= expected-application (apply-events events)))))))
 
-                (testing "> approved"
-                  (let [events (conj events
-                                     {:event/type :application.event/approved
-                                      :event/time (DateTime. 4000)
-                                      :event/actor "handler"
-                                      :application/id 1
-                                      :application/comment "looks good"})
-                        expected-application (merge expected-application
-                                                    {:application/last-activity (DateTime. 4000)
-                                                     :application/events events
-                                                     :application/state :application.state/approved})]
-                    (is (= expected-application (apply-events events)))
+                      (testing "> submitted (no draft saved)"
+                        (let [events (conj events
+                                           {:event/type :application.event/submitted
+                                            :event/time (DateTime. 7000)
+                                            :event/actor "applicant"
+                                            :application/id 1})
+                              expected-application (deep-merge expected-application
+                                                               {:application/last-activity (DateTime. 7000)
+                                                                :application/events events
+                                                                :application/state :application.state/submitted
+                                                                ;; when there was no draft-saved event, the current and
+                                                                ;; previous submitted answers must be the same
+                                                                :application/form {:form/fields [{:field/value "foo"
+                                                                                                  :field/previous-value "foo"}
+                                                                                                 {:field/value "bar"
+                                                                                                  :field/previous-value "bar"}]}})]
+                          (is (= expected-application (apply-events events)))))))
 
-                    (testing "> closed"
-                      (let [events (conj events
-                                         {:event/type :application.event/closed
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "handler"
-                                          :application/id 1
-                                          :application/comment "the project is finished"})
-                            expected-application (merge expected-application
-                                                        {:application/last-activity (DateTime. 5000)
-                                                         :application/events events
-                                                         :application/state :application.state/closed})]
-                        (is (= expected-application (apply-events events)))))))
+                  (testing "> approved"
+                    (let [events (conj events
+                                       {:event/type :application.event/approved
+                                        :event/time (DateTime. 4000)
+                                        :event/actor "handler"
+                                        :application/id 1
+                                        :application/comment "looks good"})
+                          expected-application (merge expected-application
+                                                      {:application/last-activity (DateTime. 4000)
+                                                       :application/events events
+                                                       :application/state :application.state/approved})]
+                      (is (= expected-application (apply-events events)))
 
-                (testing "> rejected"
-                  (let [events (conj events
-                                     {:event/type :application.event/rejected
-                                      :event/time (DateTime. 4000)
-                                      :event/actor "handler"
-                                      :application/id 1
-                                      :application/comment "never gonna happen"})
-                        expected-application (merge expected-application
-                                                    {:application/last-activity (DateTime. 4000)
-                                                     :application/events events
-                                                     :application/state :application.state/rejected})]
-                    (is (= expected-application (apply-events events)))))
+                      (testing "> closed"
+                        (let [events (conj events
+                                           {:event/type :application.event/closed
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "handler"
+                                            :application/id 1
+                                            :application/comment "the project is finished"})
+                              expected-application (merge expected-application
+                                                          {:application/last-activity (DateTime. 5000)
+                                                           :application/events events
+                                                           :application/state :application.state/closed})]
+                          (is (= expected-application (apply-events events)))))))
 
-                (testing "> comment requested"
-                  (let [request-id (UUID/fromString "4de6c2b0-bb2e-4745-8f92-bd1d1f1e8298")
-                        events (conj events
-                                     {:event/type :application.event/comment-requested
-                                      :event/time (DateTime. 4000)
-                                      :event/actor "handler"
-                                      :application/id 1
-                                      :application/request-id request-id
-                                      :application/commenters ["commenter"]
-                                      :application/comment "please comment"})
-                        expected-application (deep-merge expected-application
-                                                         {:application/last-activity (DateTime. 4000)
-                                                          :application/events events
-                                                          :application/workflow {:workflow.dynamic/awaiting-commenters #{"commenter"}}})]
-                    (is (= expected-application (apply-events events)))
+                  (testing "> rejected"
+                    (let [events (conj events
+                                       {:event/type :application.event/rejected
+                                        :event/time (DateTime. 4000)
+                                        :event/actor "handler"
+                                        :application/id 1
+                                        :application/comment "never gonna happen"})
+                          expected-application (merge expected-application
+                                                      {:application/last-activity (DateTime. 4000)
+                                                       :application/events events
+                                                       :application/state :application.state/rejected})]
+                      (is (= expected-application (apply-events events)))))
 
-                    (testing "> commented"
-                      (let [events (conj events
-                                         {:event/type :application.event/commented
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "commenter"
-                                          :application/id 1
-                                          :application/request-id request-id
-                                          :application/comment "looks good"})
-                            expected-application (deep-merge expected-application
-                                                             {:application/last-activity (DateTime. 5000)
-                                                              :application/events events
-                                                              :application/workflow {:workflow.dynamic/awaiting-commenters #{}}})]
-                        (is (= expected-application (apply-events events)))))))
+                  (testing "> comment requested"
+                    (let [request-id (UUID/fromString "4de6c2b0-bb2e-4745-8f92-bd1d1f1e8298")
+                          events (conj events
+                                       {:event/type :application.event/comment-requested
+                                        :event/time (DateTime. 4000)
+                                        :event/actor "handler"
+                                        :application/id 1
+                                        :application/request-id request-id
+                                        :application/commenters ["commenter"]
+                                        :application/comment "please comment"})
+                          expected-application (deep-merge expected-application
+                                                           {:application/last-activity (DateTime. 4000)
+                                                            :application/events events
+                                                            :application/workflow {:workflow.dynamic/awaiting-commenters #{"commenter"}}})]
+                      (is (= expected-application (apply-events events)))
 
-                (testing "> decision requested"
-                  (let [request-id (UUID/fromString "db9c7fd6-53be-4b04-b15d-a3a8e0a45e49")
-                        events (conj events
-                                     {:event/type :application.event/decision-requested
-                                      :event/time (DateTime. 4000)
-                                      :event/actor "handler"
-                                      :application/id 1
-                                      :application/request-id request-id
-                                      :application/deciders ["decider"]
-                                      :application/comment "please decide"})
-                        expected-application (deep-merge expected-application
-                                                         {:application/last-activity (DateTime. 4000)
-                                                          :application/events events
-                                                          :application/workflow {:workflow.dynamic/awaiting-deciders #{"decider"}}})]
-                    (is (= expected-application (apply-events events)))
+                      (testing "> commented"
+                        (let [events (conj events
+                                           {:event/type :application.event/commented
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "commenter"
+                                            :application/id 1
+                                            :application/request-id request-id
+                                            :application/comment "looks good"})
+                              expected-application (deep-merge expected-application
+                                                               {:application/last-activity (DateTime. 5000)
+                                                                :application/events events
+                                                                :application/workflow {:workflow.dynamic/awaiting-commenters #{}}})]
+                          (is (= expected-application (apply-events events)))))))
 
-                    (testing "> decided"
-                      (let [events (conj events
-                                         {:event/type :application.event/decided
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "decider"
-                                          :application/id 1
-                                          :application/request-id request-id
-                                          :application/decision :approved
-                                          :application/comment "I approve this"})
-                            expected-application (deep-merge expected-application
-                                                             {:application/last-activity (DateTime. 5000)
-                                                              :application/events events
-                                                              :application/workflow {:workflow.dynamic/awaiting-deciders #{}}})]
-                        (is (= expected-application (apply-events events)))))))
+                  (testing "> decision requested"
+                    (let [request-id (UUID/fromString "db9c7fd6-53be-4b04-b15d-a3a8e0a45e49")
+                          events (conj events
+                                       {:event/type :application.event/decision-requested
+                                        :event/time (DateTime. 4000)
+                                        :event/actor "handler"
+                                        :application/id 1
+                                        :application/request-id request-id
+                                        :application/deciders ["decider"]
+                                        :application/comment "please decide"})
+                          expected-application (deep-merge expected-application
+                                                           {:application/last-activity (DateTime. 4000)
+                                                            :application/events events
+                                                            :application/workflow {:workflow.dynamic/awaiting-deciders #{"decider"}}})]
+                      (is (= expected-application (apply-events events)))
 
-                (testing "> member invited"
-                  (let [token "b187bda7b9da9053a5d8b815b029e4ba"
-                        events (conj events
-                                     {:event/type :application.event/member-invited
-                                      :event/time (DateTime. 4000)
-                                      :event/actor "applicant"
-                                      :application/id 1
-                                      :application/member {:name "Mr. Member"
-                                                           :email "member@example.com"}
-                                      :invitation/token token})
-                        expected-application (deep-merge expected-application
-                                                         {:application/last-activity (DateTime. 4000)
-                                                          :application/events events
-                                                          :application/invitation-tokens {token {:name "Mr. Member"
-                                                                                                 :email "member@example.com"}}})]
-                    (is (= expected-application (apply-events events)))
+                      (testing "> decided"
+                        (let [events (conj events
+                                           {:event/type :application.event/decided
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "decider"
+                                            :application/id 1
+                                            :application/request-id request-id
+                                            :application/decision :approved
+                                            :application/comment "I approve this"})
+                              expected-application (deep-merge expected-application
+                                                               {:application/last-activity (DateTime. 5000)
+                                                                :application/events events
+                                                                :application/workflow {:workflow.dynamic/awaiting-deciders #{}}})]
+                          (is (= expected-application (apply-events events)))))))
 
-                    (testing "> member uninvited"
-                      (let [events (conj events
-                                         {:event/type :application.event/member-uninvited
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "applicant"
-                                          :application/id 1
-                                          :application/member {:name "Mr. Member"
-                                                               :email "member@example.com"}
-                                          :application/comment "he left the project"})
-                            expected-application (merge expected-application
-                                                        {:application/last-activity (DateTime. 5000)
-                                                         :application/events events
-                                                         :application/invitation-tokens {}})]
-                        (is (= expected-application (apply-events events)))))
+                  (testing "> member invited"
+                    (let [token "b187bda7b9da9053a5d8b815b029e4ba"
+                          events (conj events
+                                       {:event/type :application.event/member-invited
+                                        :event/time (DateTime. 4000)
+                                        :event/actor "applicant"
+                                        :application/id 1
+                                        :application/member {:name "Mr. Member"
+                                                             :email "member@example.com"}
+                                        :invitation/token token})
+                          expected-application (deep-merge expected-application
+                                                           {:application/last-activity (DateTime. 4000)
+                                                            :application/events events
+                                                            :application/invitation-tokens {token {:name "Mr. Member"
+                                                                                                   :email "member@example.com"}}})]
+                      (is (= expected-application (apply-events events)))
 
-                    (testing "> member joined"
-                      (let [events (conj events
-                                         {:event/type :application.event/member-joined
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "member"
-                                          :application/id 1
-                                          :invitation/token token})
-                            expected-application (merge expected-application
-                                                        {:application/last-activity (DateTime. 5000)
-                                                         :application/events events
-                                                         :application/members #{{:userid "member"}}
-                                                         :application/invitation-tokens {}})]
-                        (is (= expected-application (apply-events events)))))))
+                      (testing "> member uninvited"
+                        (let [events (conj events
+                                           {:event/type :application.event/member-uninvited
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "applicant"
+                                            :application/id 1
+                                            :application/member {:name "Mr. Member"
+                                                                 :email "member@example.com"}
+                                            :application/comment "he left the project"})
+                              expected-application (merge expected-application
+                                                          {:application/last-activity (DateTime. 5000)
+                                                           :application/events events
+                                                           :application/invitation-tokens {}})]
+                          (is (= expected-application (apply-events events)))))
 
-                (testing "> member added"
-                  (let [events (conj events
-                                     {:event/type :application.event/member-added
-                                      :event/time (DateTime. 4000)
-                                      :event/actor "handler"
-                                      :application/id 1
-                                      :application/member {:userid "member"}})
-                        expected-application (merge expected-application
-                                                    {:application/last-activity (DateTime. 4000)
-                                                     :application/events events
-                                                     :application/members #{{:userid "member"}}})]
-                    (is (= expected-application (apply-events events)))
+                      (testing "> member joined"
+                        (let [events (conj events
+                                           {:event/type :application.event/member-joined
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "member"
+                                            :application/id 1
+                                            :invitation/token token})
+                              expected-application (merge expected-application
+                                                          {:application/last-activity (DateTime. 5000)
+                                                           :application/events events
+                                                           :application/members #{{:userid "member"}}
+                                                           :application/invitation-tokens {}})]
+                          (is (= expected-application (apply-events events)))))))
 
-                    (testing "> member removed"
-                      (let [events (conj events
-                                         {:event/type :application.event/member-removed
-                                          :event/time (DateTime. 5000)
-                                          :event/actor "applicant"
-                                          :application/id 1
-                                          :application/member {:userid "member"}
-                                          :application/comment "he left the project"})
-                            expected-application (merge expected-application
-                                                        {:application/last-activity (DateTime. 5000)
-                                                         :application/events events
-                                                         :application/members #{}})]
-                        (is (= expected-application (apply-events events)))))))))))))))
+                  (testing "> member added"
+                    (let [events (conj events
+                                       {:event/type :application.event/member-added
+                                        :event/time (DateTime. 4000)
+                                        :event/actor "handler"
+                                        :application/id 1
+                                        :application/member {:userid "member"}})
+                          expected-application (merge expected-application
+                                                      {:application/last-activity (DateTime. 4000)
+                                                       :application/events events
+                                                       :application/members #{{:userid "member"}}})]
+                      (is (= expected-application (apply-events events)))
+
+                      (testing "> member removed"
+                        (let [events (conj events
+                                           {:event/type :application.event/member-removed
+                                            :event/time (DateTime. 5000)
+                                            :event/actor "applicant"
+                                            :application/id 1
+                                            :application/member {:userid "member"}
+                                            :application/comment "he left the project"})
+                              expected-application (merge expected-application
+                                                          {:application/last-activity (DateTime. 5000)
+                                                           :application/events events
+                                                           :application/members #{}})]
+                          (is (= expected-application (apply-events events))))))))))))))))
 
 (deftest test-calculate-permissions
   ;; TODO: is this what we want? wouldn't it be useful to be able to write more than one comment?
diff --git a/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj b/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
index bafab7ad0..04b9c00a9 100644
--- a/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
+++ b/test/clj/rems/migrations/test_convert_to_dynamic_applications.clj
@@ -147,8 +147,13 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "draft application"
                                                            6 "draft application"
                                                            7 "draft appl"
-                                                           8 "draft application"}
-                                :application/accepted-licenses #{1 2}}]
+                                                           8 "draft application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id 1
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}]
               :workflow {:type :workflow/dynamic
                          :handlers ["developer"]}}
              (select-keys application [:id :description :applicantuserid :dynamic-events :workflow]))))
@@ -184,11 +189,16 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "applied application"
                                                            6 "applied application"
                                                            7 "applied ap"
-                                                           8 "applied application"}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 "applied application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}]
               :workflow {:type :workflow/dynamic
@@ -226,16 +236,21 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "rejected application"
                                                            6 "rejected application"
                                                            7 "rejected a"
-                                                           8 "rejected application"}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 "rejected application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/rejected
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "comment for rejection"}]
@@ -274,16 +289,21 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "accepted application"
                                                            6 "accepted application"
                                                            7 "accepted a"
-                                                           8 "accepted application"}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 "accepted application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id 4
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/approved
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "comment for approval"}]
@@ -322,16 +342,21 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "returned application"
                                                            6 "returned application"
                                                            7 "returned a"
-                                                           8 "returned application"}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 "returned application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "developer"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/returned
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "comment for return"}]
@@ -373,22 +398,27 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "bundled application"
                                                            6 "bundled application"
                                                            7 "bundled ap"
-                                                           8 "bundled application"}
-                                :application/accepted-licenses #{1 2 3}}
+                                                           8 "bundled application"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2 3}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/returned
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "comment for return"}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}]
               :workflow {:type :workflow/dynamic
@@ -426,23 +456,28 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "application with review"
                                                            6 "application with review"
                                                            7 "applicatio"
-                                                           8 "application with review"}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 "application with review"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/commented
                                 :event/actor "carl"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/request-id (UUID/fromString "00000000-0000-0000-0000-000000000000")
                                 :application/comment "comment for review"}
                                {:event/type :application.event/approved
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "comment for approval"}]
@@ -481,11 +516,16 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 "application in review"
                                                            6 "application in review"
                                                            7 "applicatio"
-                                                           8 "application in review"}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 "application in review"}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time test-data/creation-time
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}]
               :workflow {:type :workflow/dynamic
@@ -523,16 +563,21 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 ""
                                                            6 ""
                                                            7 ""
-                                                           8 ""}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/approved
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment ""}]
@@ -542,7 +587,7 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
 
     (let [app-id 21
           application (applications/get-application-state app-id)
-          request-id (-> application :dynamic-events (nth 3) :application/request-id)]
+          request-id (-> application :dynamic-events (nth 4) :application/request-id)]
       (is request-id)
       (is (= {:id app-id
               :description "third party review"
@@ -573,16 +618,21 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 ""
                                                            6 ""
                                                            7 ""
-                                                           8 ""}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/comment-requested
                                 :event/actor "carl" ;; TODO: should really be "developer" but the old event doesn't tell it
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/request-id request-id
@@ -590,7 +640,7 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                 :application/comment "plz comment"}
                                {:event/type :application.event/commented
                                 :event/actor "carl"
-                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/request-id request-id
@@ -630,16 +680,21 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 ""
                                                            6 ""
                                                            7 ""
-                                                           8 ""}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/returned
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "nope nope nope"}]
@@ -678,22 +733,27 @@ INSERT INTO entitlement (resid, catappid, userid, start, endt) VALUES (1, 23, 'a
                                                            5 ""
                                                            6 ""
                                                            7 ""
-                                                           8 ""}
-                                :application/accepted-licenses #{1 2}}
+                                                           8 ""}}
+                               {:event/type :application.event/licenses-accepted
+                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/actor "alice"
+                                :application/id app-id
+                                :application/accepted-licenses #{1 2}
+                                :event/id (next-event-id)}
                                {:event/type :application.event/submitted
                                 :event/actor "alice"
-                                :event/time (-> application :dynamic-events (nth 2) :event/time)
+                                :event/time (-> application :dynamic-events (nth 3) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id}
                                {:event/type :application.event/approved
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 3) :event/time)
+                                :event/time (-> application :dynamic-events (nth 4) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment ""}
                                {:event/type :application.event/closed
                                 :event/actor "developer"
-                                :event/time (-> application :dynamic-events (nth 4) :event/time)
+                                :event/time (-> application :dynamic-events (nth 5) :event/time)
                                 :event/id (next-event-id)
                                 :application/id app-id
                                 :application/comment "no more"}]
diff --git a/test/clj/rems/test_browser.clj b/test/clj/rems/test_browser.clj
index ef8c672d0..9c4d8cd97 100644
--- a/test/clj/rems/test_browser.clj
+++ b/test/clj/rems/test_browser.clj
@@ -152,12 +152,11 @@
   ;; XXX: assumes that the checkbox is unchecked
   (click-visible *driver* [{:css (str "input[value='" value "']")}]))
 
-(defn accept-license [label]
-  ;; XXX: assumes that the checkbox is unchecked
-  (click-visible *driver* [:licenses
-                           {:tag :a, :fn/text label}
-                           {:xpath "./ancestor::div[@class='license']"}
-                           {:css "input[type='checkbox']"}]))
+(defn accept-licenses []
+  (doto *driver*
+    (click-visible :accept-licenses-button)
+    (wait-visible :status-success)
+    (click-visible :modal-ok)))
 
 (defn send-application []
   (doto *driver*
@@ -184,7 +183,7 @@
 
 (deftest test-new-application
   (with-postmortem *driver* {:dir reporting-dir}
-    (login-as "developer")
+    (login-as "alice")
 
     (go-to-catalogue)
     (add-to-cart "THL catalogue item")
@@ -210,17 +209,17 @@
     (fill-form-field "21. Funding information" "Test")
     (fill-form-field "22. Invoice address (Service prices: www.thl.fi/biobank/researchers)" "Test")
     (check-box "disease_prevention")
-    (accept-license "CC Attribution 4.0")
-    (accept-license "General Terms of Use")
 
     (send-application)
     (is (= "State: Applied" (get-element-text *driver* :application-state)))
 
+    (accept-licenses)
+
     (let [application-id (get-application-id)]
       (go-to-applications)
       (let [summary (get-application-summary application-id)]
         (is (= "THL catalogue item" (:resource summary)))
-        (is (= "developer" (:applicant summary)))
+        (is (= "alice" (:applicant summary)))
         (is (= "Applied" (:state summary)))
         ;; don't bother trying to predict the external id:
         (is (.contains (:description summary) "Test name"))))))
diff --git a/test/clj/rems/workflow/test_dynamic.clj b/test/clj/rems/workflow/test_dynamic.clj
index 473a03509..df46ff324 100644
--- a/test/clj/rems/workflow/test_dynamic.clj
+++ b/test/clj/rems/workflow/test_dynamic.clj
@@ -84,25 +84,21 @@
                :event/time test-time
                :event/actor applicant-user-id
                :application/id 123
-               :application/field-values {1 "foo" 2 "bar"}
-               :application/accepted-licenses #{1 2}}]
+               :application/field-values {1 "foo" 2 "bar"}}]
              (ok-command application
                          {:type :application.command/save-draft
                           :actor applicant-user-id
-                          :field-values {1 "foo" 2 "bar"}
-                          :accepted-licenses #{1 2}}))))
+                          :field-values {1 "foo" 2 "bar"}}))))
     (testing "only the applicant can save a draft"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application
                            {:type :application.command/save-draft
                             :actor "non-applicant"
-                            :field-values {1 "foo" 2 "bar"}
-                            :accepted-licenses #{1 2}})
+                            :field-values {1 "foo" 2 "bar"}})
              (fail-command application
                            {:type :application.command/save-draft
                             :actor handler-user-id
-                            :field-values {1 "foo" 2 "bar"}
-                            :accepted-licenses #{1 2}}))))
+                            :field-values {1 "foo" 2 "bar"}}))))
     (testing "draft cannot be updated after submitting"
       (let [application (apply-events application
                                       [{:event/type :application.event/submitted
@@ -113,8 +109,7 @@
                (fail-command application
                              {:type :application.command/save-draft
                               :actor applicant-user-id
-                              :field-values {1 "updated"}
-                              :accepted-licenses #{3}})))))
+                              :field-values {1 "updated"}})))))
     (testing "draft can be updated after returning it to applicant"
       (let [application (apply-events application
                                       [{:event/type :application.event/submitted
@@ -130,13 +125,24 @@
                  :event/time test-time
                  :event/actor applicant-user-id
                  :application/id 123
-                 :application/field-values {1 "updated"}
-                 :application/accepted-licenses #{3}}]
+                 :application/field-values {1 "updated"}}]
                (ok-command application
                            {:type :application.command/save-draft
                             :actor applicant-user-id
-                            :field-values {1 "updated"}
-                            :accepted-licenses #{3}})))))))
+                            :field-values {1 "updated"}})))))))
+
+(deftest test-accept-licenses
+  (let [application (apply-events nil [dummy-created-event])]
+    (testing "accepts licenses"
+      (is (= [{:event/type :application.event/licenses-accepted
+               :event/time test-time
+               :event/actor applicant-user-id
+               :application/id 123
+               :application/accepted-licenses #{1 2}}]
+             (ok-command application
+                         {:type :application.command/accept-licenses
+                          :actor applicant-user-id
+                          :accepted-licenses #{1 2}}))))))
 
 (deftest test-submit
   (let [injections {:validate-form-answers fake-validate-form-answers}
@@ -160,8 +166,7 @@
                            :event/actor applicant-user-id
                            :application/id 123
                            :application/field-values {41 "foo"
-                                                      42 "bar"}
-                           :application/accepted-licenses #{30 31}}
+                                                      42 "bar"}}
         submit-command {:type :application.command/submit
                         :actor applicant-user-id}
         application (apply-events nil [created-event draft-saved-event])]
@@ -180,13 +185,6 @@
                  (apply-events [(assoc-in draft-saved-event [:application/field-values 41] "")])
                  (fail-command submit-command injections)))))
 
-    (testing "cannot submit when not all licenses are accepted"
-      (is (= {:errors [{:type :t.form.validation/required
-                        :license-id 31}]}
-             (-> application
-                 (apply-events [(update-in draft-saved-event [:application/accepted-licenses] disj 31)])
-                 (fail-command submit-command injections)))))
-
     (testing "non-applicant cannot submit"
       (is (= {:errors [{:type :forbidden}]}
              (fail-command application