Considerable progress on the issue experts workflow, not perfect yet.

This commit is contained in:
Simon Brooke 2018-09-03 12:45:48 +01:00
parent 3df314ecfc
commit 54ad57349c
4 changed files with 100 additions and 14 deletions

View file

@ -67,6 +67,13 @@ WHERE canvasser_id = :id
ORDER BY date desc ORDER BY date desc
LIMIT 1 LIMIT 1
--:name get-locality-for-visit :? :1
--:doc returns the locality of the address of this visit
SELECT addresses.locality
FROM addresses, visits
WHERE visits.address_id = addresses.id
AND visits.id = :id
-- I don't know why this next one isn't autogenerating, but it isn't and it's critical. -- I don't know why this next one isn't autogenerating, but it isn't and it's critical.
-- :name list-roles-by-canvasser :? :* -- :name list-roles-by-canvasser :? :*

View file

@ -0,0 +1,25 @@
------------------------------------------------------------------------
-- convenience view lv_followupactions of entity followupactions for
-- lists, et cetera
-- ADL is not yet correctly chaining tables when generating convenience
-- views, so the auto-generated convenience view is a horrible
-- cross-product join
------------------------------------------------------------------------
DROP VIEW lv_followupactions;
CREATE VIEW lv_followupactions AS
SELECT electors.name ||', '|| addresses.address ||', '|| addresses.postcode ||', '|| visits.date ||', '|| issues.id AS request_id_expanded,
followupactions.request_id,
canvassers.username ||', '|| canvassers.fullname ||', '|| addresses.address ||', '|| addresses.postcode ||', '|| canvassers.phone ||', '|| canvassers.email AS actor_expanded,
followupactions.actor,
followupactions.date,
followupactions.notes,
followupactions.closed,
followupactions.id
FROM followuprequests, visits, canvassers, addresses, followupactions, issues, electors
WHERE followupactions.request_id = followuprequests.id
AND followuprequests.elector_id = electors.id
AND followuprequests.visit_id = visits.id
AND followuprequests.issue_id = issues.id
AND visits.address_id = addresses.id
AND followupactions.actor = canvassers.id
;

View file

@ -13,16 +13,19 @@
<form action='{{servlet-context}}/issue-expert/followup-action' method='POST'> <form action='{{servlet-context}}/issue-expert/followup-action' method='POST'>
{% csrf-field %} {% csrf-field %}
<input id='id' name='id' type='hidden' value='{{record.id}}'/> <input id='id' name='id' type='hidden' value='{{record.id}}'/>
<input id='request_id' name='request_id' type='hidden' value='{{record.id}}'/>
<input id='elector_id' name='elector_id' type='hidden' value='{{record.elector_id}}'/>
<input id='visit_id' name='visit_id' type='hidden' value='{{record.visit_id}}'/>
<p class='widget'> <p class='widget'>
<label for='elector_id'> <label for='elector_id'>
Elector Elector
</label> </label>
{% ifmemberof canvassers teamorganisers issueexperts analysts issueeditors admin %} {% ifmemberof canvassers teamorganisers issueexperts analysts issueeditors admin %}
<span id='elector_id' name='elector_id' class='pseudo-widget disabled'> <span id='elector' name='elector' class='pseudo-widget disabled'>
{{elector.name}} ({{elector.gender}}) {{elector.name}} ({{elector.gender}})
</span> </span>
{% else %} {% else %}
<span id='elector_id' name='elector_id' class='pseudo-widget not-authorised'> <span id='elector' name='elector' class='pseudo-widget not-authorised'>
You are not permitted to view elector of followuprequests You are not permitted to view elector of followuprequests
</span> </span>
{% endifmemberof %} {% endifmemberof %}
@ -121,7 +124,21 @@
<input id='closed' name='closed' type='checkbox' maxlength='' size='16'/> <input id='closed' name='closed' type='checkbox' maxlength='' size='16'/>
{% endifmemberof %} {% endifmemberof %}
</p> </p>
<p class='widget'>
<label for='intention'>
What is the elector's voting intention now?
</label>
{% ifmemberof admin issueexperts %}
<select id='option_id' name='option_id'>
<option value="">Not stated</option>
{% for option in options %}
<img src="{{servlet-context}}/img/option/{{option.id}}-{% ifequal record.option_id option.id %}selected{% else %}unselected{% endifequal %}.png" alt="{{option.id}}"/>
<option value='{{option.id}}' {% ifequal record.option_id option.id%}selected='selected'{% endifequal %} style="background-image: url('{{servlet-context}}/img/option/{{option.id}}-{% ifequal record.option_id option.id %}selected{% else %}unselected{% endifequal %}.png')">
{{option.id}}</option>
{% endfor %}
</select>
{% endifmemberof %}
</p>
{% ifmemberof admin issueexperts %} {% ifmemberof admin issueexperts %}
<p class='widget action-safe'> <p class='widget action-safe'>
<label for='save-button' class='action-safe'> <label for='save-button' class='action-safe'>

View file

@ -6,6 +6,7 @@
[clojure.string :as s] [clojure.string :as s]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[clojure.walk :refer [keywordize-keys]] [clojure.walk :refer [keywordize-keys]]
[java-time :as jt]
[markdown.core :refer [md-to-html-string]] [markdown.core :refer [md-to-html-string]]
[noir.util.route :as route] [noir.util.route :as route]
[ring.util.http-response :as response] [ring.util.http-response :as response]
@ -39,6 +40,11 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; TODO: Must lock record to expert when taking it from the list, unlock after
;;; thirty minutes or when the action is posted, whichever is sooner. If
;;; an expert opens a locked record, it must show up as locked. Also, expert should
;;; not be able to open a closed request.
(defn list-page [request] (defn list-page [request]
(layout/render (layout/render
"issue-expert/list.html" "issue-expert/list.html"
@ -48,10 +54,10 @@
:records (db/list-open-requests db/*db* {:expert (:id user)})}))) :records (db/list-open-requests db/*db* {:expert (:id user)})})))
(defn followup-request-page [request] (defn get-followup-request-page [request]
(let (let
[params (support/massage-params request) [params (support/massage-params request)
id (:id (keywordize-keys params)) id (:id params)
record (db/get-followuprequest db/*db* {:id id}) record (db/get-followuprequest db/*db* {:id id})
elector (if elector (if
record record
@ -65,15 +71,11 @@
db/*db* {:id (:visit_id record)})))] db/*db* {:id (:visit_id record)})))]
(layout/render (layout/render
"issue-expert/request.html" "issue-expert/request.html"
{:title (str "Request from " (:name elector) " at " (:date visit)) {:actions (map
:user (:user (:session request))
:visit visit
:actions (map
;; HTML-ise the notes in each action record ;; HTML-ise the notes in each action record
#(merge % {:notes (md-to-html-string (:notes %))}) #(merge % {:notes (md-to-html-string (:notes %))})
(db/list-followupactions-by-followuprequest (db/list-followupactions-by-followuprequest
db/*db* {:id id})) db/*db* {:id id}))
:record record
:elector elector :elector elector
:issue (let :issue (let
[raw-issue (if [raw-issue (if
@ -82,13 +84,48 @@
(if raw-issue (if raw-issue
(merge (merge
raw-issue raw-issue
{:brief (md-to-html-string (:brief raw-issue))})))}))) {:brief (md-to-html-string (:brief raw-issue))})))
:options (db/list-options db/*db* params)
:record record
:title (str "Request from " (:name elector) " at " (:date visit))
:user (:user (:session request))
:visit visit})))
(defn post-followup-action
"From this `request`, create a `followupaction` record, and, if an
`option_id` is present in the params, an `intention` record; show
the request list on success, to the request form on failure."
[request]
(support/do-or-log-error
(let
[params (support/massage-params request)
locality (:locality (db/get-locality-for-visit db/*db* {:id (:visit_id params)}))]
(log/debug "post-followup-request-page with request " request)
(db/create-followupaction!
db/*db*
(assoc
params
:actor (:id (:user (:session request)))
:date (jt/to-sql-timestamp (jt/local-date-time))
:closed (= (:closed params) "on")))
(if-not
(zero? (count (:option_id params)))
(if
(zero? (count (:signature (db/get-elector db/*db* {:id (:elector_id params)}))))
;; the elector has NOT recorded GDPR consent: explicitly bind elector_id to nil
(db/create-intention! db/*db* (assoc params :locality locality :elector_id nil))
;; else the elector HAS recorded GDPR consent
(db/create-intention! db/*db* (assoc params :locality locality))))
(list-page request))
:error-return
(get-followup-request-page request)))
(defroutes issue-expert-routes (defroutes issue-expert-routes
(GET "/issue-expert/list" request (GET "/issue-expert/list" request
(route/restricted (list-page request))) (route/restricted (list-page request)))
(GET "/issue-expert/followup-request" request (GET "/issue-expert/followup-request" request
(route/restricted (followup-request-page request))) (route/restricted (get-followup-request-page request)))
(POST "/issue-expert/followup-request" request (POST "/issue-expert/followup-action" request
(route/restricted (followup-request-page request)))) (route/restricted (post-followup-action request))))