renalware-core 2.0.24 → 2.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -5
- data/app/assets/images/renalware/print-button-example.png +0 -0
- data/app/assets/stylesheets/renalware/partials/_layout.scss +4 -0
- data/app/assets/stylesheets/renalware/pdf.scss +12 -0
- data/app/assets/stylesheets/renalware/protocol_pdf.scss +4 -0
- data/app/assets/stylesheets/renalware/table_pdf.scss +210 -0
- data/app/controllers/renalware/admissions/consults_controller.rb +24 -6
- data/app/controllers/renalware/concerns/pdf_renderable.rb +11 -2
- data/app/controllers/renalware/renal/aki_alerts_controller.rb +29 -7
- data/app/controllers/renalware/transplants/registration_statuses_controller.rb +1 -1
- data/app/models/renalware/admissions/consult_query.rb +3 -0
- data/app/models/renalware/feeds/message_processor.rb +5 -0
- data/app/models/renalware/feeds/persist_message.rb +4 -1
- data/app/models/renalware/renal.rb +1 -1
- data/app/models/renalware/renal/aki_alert.rb +1 -0
- data/app/presenters/renalware/admissions/admission_presenter.rb +1 -1
- data/app/views/renalware/admissions/consults/_filters.html.slim +10 -1
- data/app/views/renalware/admissions/consults/_filters.pdf.slim +32 -0
- data/app/views/renalware/admissions/consults/_table.html.slim +9 -8
- data/app/views/renalware/admissions/consults/_table.pdf.slim +35 -0
- data/app/views/renalware/admissions/consults/index.html.slim +7 -0
- data/app/views/renalware/admissions/consults/index.pdf.slim +10 -0
- data/app/views/renalware/hd/sessions/_empty_row.html.slim +2 -0
- data/app/views/renalware/hd/sessions/_row.html.slim +3 -0
- data/app/views/renalware/hd/sessions/_show.html.slim +5 -6
- data/app/views/renalware/hd/sessions/_thead.html.slim +4 -0
- data/app/views/renalware/layouts/pdf.pdf.slim +1 -1
- data/app/views/renalware/letters/contacts/_edit.html.slim +1 -1
- data/app/views/renalware/renal/aki_alerts/_filters.pdf.slim +25 -0
- data/app/views/renalware/renal/aki_alerts/_table.pdf.slim +29 -0
- data/app/views/renalware/renal/aki_alerts/edit.html.slim +1 -1
- data/app/views/renalware/renal/aki_alerts/index.html.slim +10 -0
- data/app/views/renalware/renal/aki_alerts/index.pdf.slim +14 -0
- data/app/views/renalware/shared/_please_print_using_print_button_warning.html.slim +18 -0
- data/app/views/renalware/transplants/donor_followups/_form.html.slim +2 -2
- data/app/views/renalware/transplants/recipient_followups/_form.html.slim +6 -1
- data/app/views/renalware/transplants/registration_statuses/_inputs.html.slim +4 -0
- data/app/views/renalware/transplants/registration_statuses/_list.html.slim +18 -5
- data/config/locales/custom.yml +2 -3
- data/config/locales/renalware/hd/session.en.yml +3 -0
- data/db/migrate/20180510151959_update_hd_overall_audit_to_version_7.rb +6 -0
- data/db/migrate/20180511100345_add_notes_to_transplant_registration_statuses.rb +5 -0
- data/db/migrate/20180511140415_add_message_hash_messaging_messages.rb +6 -0
- data/db/seeds/default/deaths/death_causes.rb +4 -4
- data/db/seeds/default/patients/patients_languages.rb +4 -3
- data/db/seeds/default/renal/prd_descriptions.rb +7 -5
- data/db/seeds/default/system/countries.rb +10 -6
- data/db/views/reporting_hd_overall_audit_v07.sql +44 -0
- data/lib/renalware/configuration.rb +4 -0
- data/lib/renalware/engine.rb +2 -0
- data/lib/renalware/version.rb +1 -1
- data/spec/factories/admissions/consults.rb +12 -0
- data/spec/support/capybara.rb +16 -21
- metadata +15 -3
- data/spec/support/poltergeist.rb +0 -4
@@ -10,6 +10,7 @@ module Renalware
|
|
10
10
|
def initialize(query = nil)
|
11
11
|
@query = query || {}
|
12
12
|
@query[:ended_on_null] ||= true
|
13
|
+
@query[:s] ||= "hospital_ward_name"
|
13
14
|
end
|
14
15
|
|
15
16
|
def call
|
@@ -20,6 +21,7 @@ module Renalware
|
|
20
21
|
# It might be better to refactor PatientsRansackHelper so we can include where required
|
21
22
|
# eg below using .extending(PatientsRansackHelper) rather than relying on it being in
|
22
23
|
# included in the model file.
|
24
|
+
# rubocop:disable Metrics/MethodLength
|
23
25
|
def search
|
24
26
|
@search ||= begin
|
25
27
|
Consult
|
@@ -35,6 +37,7 @@ module Renalware
|
|
35
37
|
.ransack(query)
|
36
38
|
end
|
37
39
|
end
|
40
|
+
# rubocop:enable Metrics/MethodLength
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
@@ -27,6 +27,11 @@ module Renalware
|
|
27
27
|
MessageParser.new.parse(raw_message)
|
28
28
|
end
|
29
29
|
|
30
|
+
# If the incoming message has already been processed we should not be processing it again.
|
31
|
+
# To help enforce this there is a unique MD5 hash on feed_messages which will baulk if the
|
32
|
+
# same message payload is saved twice - in this case we exit #call early, the broadcast
|
33
|
+
# is not issued and therefore the message is not processed. The message will go back into
|
34
|
+
# the delayed_job queue and retry, failing until it finally gives up!
|
30
35
|
def persist_message(message_payload)
|
31
36
|
PersistMessage.new.call(message_payload)
|
32
37
|
end
|
@@ -6,11 +6,14 @@ module Renalware
|
|
6
6
|
module Feeds
|
7
7
|
class PersistMessage
|
8
8
|
# message_payload is an HL7Message (a decorator around ::HL7::Message)
|
9
|
+
# If the same message is persisted twice we'll get an ActiveRecord::RecordNotUnique error
|
10
|
+
# but that's fine as we don't want to process the same HL7 message twice.
|
9
11
|
def call(message_payload)
|
10
12
|
Message.create!(
|
11
13
|
event_code: message_payload.type,
|
12
14
|
header_id: message_payload.header_id,
|
13
|
-
body: message_payload.to_s
|
15
|
+
body: message_payload.to_s,
|
16
|
+
body_hash: Digest::MD5.hexdigest(message_payload.to_s)
|
14
17
|
)
|
15
18
|
end
|
16
19
|
end
|
@@ -11,11 +11,16 @@
|
|
11
11
|
= f.input :consult_site_id_eq,
|
12
12
|
collection: Renalware::Admissions::ConsultSite.pluck(:name, :id),
|
13
13
|
label: "Site"
|
14
|
-
.columns.medium-
|
14
|
+
.columns.medium-1.large-1
|
15
15
|
= f.input :seen_by_id_eq,
|
16
16
|
include_blank: false,
|
17
17
|
collection: [["Anyone", nil], ["Me", current_user.id]],
|
18
18
|
label: "Seen by"
|
19
|
+
.columns.medium-1.large-1
|
20
|
+
= f.input :rrt_eq,
|
21
|
+
include_blank: true,
|
22
|
+
collection: [["Yes", true], ["No", false]],
|
23
|
+
label: "RRT"
|
19
24
|
.columns.medium-1.large-1
|
20
25
|
= f.input :aki_risk_eq,
|
21
26
|
include_blank: true,
|
@@ -26,6 +31,10 @@
|
|
26
31
|
include_blank: true,
|
27
32
|
collection: [["Yes", true], ["No", false]],
|
28
33
|
label: "Active"
|
34
|
+
.columns.medium-2.large-2
|
35
|
+
= f.input :patient_current_modality_description_id_eq,
|
36
|
+
collection: Renalware::Modalities::Description.pluck("name", "id"),
|
37
|
+
label: "Modality"
|
29
38
|
.columns.medium-2.large-2.actions.end
|
30
39
|
= f.submit "Filter", class: "button"
|
31
40
|
span= " or "
|
@@ -0,0 +1,32 @@
|
|
1
|
+
// Compile a summary of filters applied. Leave out anything not set.
|
2
|
+
- filters = params.fetch(:q, {}).reject{ |key, value| value.blank? }
|
3
|
+
|
4
|
+
.filter-summary
|
5
|
+
table
|
6
|
+
tr
|
7
|
+
th Filters
|
8
|
+
- if filters.empty?
|
9
|
+
td None
|
10
|
+
- else
|
11
|
+
- if filters[:identity_match].present?
|
12
|
+
th Query:
|
13
|
+
td= filters[:identity_match]
|
14
|
+
- if filters[:consult_site_id_eq].present?
|
15
|
+
th Site:
|
16
|
+
td= Renalware::Admissions::ConsultSite.find_by(id: filters[:consult_site_id_eq]).name
|
17
|
+
- if filters[:rrt_eq].present?
|
18
|
+
th RRT:
|
19
|
+
td= yes_no(filters[:rrt_eq] == "true")
|
20
|
+
- if filters[:seen_by_id_eq].present?
|
21
|
+
th Seen By:
|
22
|
+
td= Renalware::User.find(filters[:seen_by_id_eq])&.to_s
|
23
|
+
- if filters[:aki_risk_eq].present?
|
24
|
+
th AKI Risk:
|
25
|
+
td= yes_no(filters[:ended_on_null]&.humanize)
|
26
|
+
- if filters[:ended_on_null].present?
|
27
|
+
th Active:
|
28
|
+
td= yes_no(filters[:ended_on_null] == "true")
|
29
|
+
- modality_id = filters[:patient_current_modality_description_id_eq]
|
30
|
+
- if modality_id.present?
|
31
|
+
th Modality:
|
32
|
+
td= Renalware::Modalities::Description.where(id: modality_id).first&.name
|
@@ -7,10 +7,8 @@
|
|
7
7
|
= sort_link(query, :patient, ["patient_family_name", "patient_given_name asc"], "Patient")
|
8
8
|
th.col-width-nhs-no.noprint NHS No.
|
9
9
|
th.col-width-reference-no Hosp Nos.
|
10
|
-
th.col-width-medium= sort_link(query,
|
11
|
-
|
12
|
-
[:consult_site_name, :hospital_ward_name],
|
13
|
-
"Location")
|
10
|
+
th.col-width-medium= sort_link(query, :hospital_ward_name, "Ward")
|
11
|
+
th.col-width-tiny RRT
|
14
12
|
th.col-width-date.noprint= sort_link(query, :started_on, "Started")
|
15
13
|
th.col-width-date.noprint= sort_link(query, :ended_on, "Ended")
|
16
14
|
th.col-width-small.show-for-large-up
|
@@ -19,13 +17,11 @@
|
|
19
17
|
th.col-width-date.show-for-large-up= sort_link(query, :patient_born_on, "DOB")
|
20
18
|
th.col-width-tiny.show-for-large-up Age
|
21
19
|
th.col-width-tiny.noprint= sort_link(query, :aki_risk, "AKI Risk")
|
22
|
-
/th.show-for-large-up Author
|
23
|
-
/th.col-width-medium.show-for-xlarge-up Description
|
24
20
|
|
25
21
|
tbody
|
26
22
|
- consults.each do |consult|
|
27
23
|
- uid = "consult-#{consult.id}"
|
28
|
-
tr
|
24
|
+
tr.keep-with-next
|
29
25
|
td.actions-dropdown.going-right.noprint
|
30
26
|
/ As we have lots of possible actions, group them in a button group.
|
31
27
|
/ A wrapping div is required for now in order for the button group display correctly
|
@@ -61,7 +57,8 @@
|
|
61
57
|
td= default_patient_link(consult.patient)
|
62
58
|
td.noprint= consult.patient_nhs_number
|
63
59
|
td= consult.patient_hospital_identifiers&.to_s_multiline
|
64
|
-
td= consult.
|
60
|
+
td= consult.hospital_ward
|
61
|
+
td= yes_no(consult.rrt?)
|
65
62
|
td.noprint= l(consult.started_on)
|
66
63
|
td.noprint= l(consult.ended_on)
|
67
64
|
td.show-for-large-up.col-width-medium-with-ellipsis= consult.patient_current_modality
|
@@ -82,3 +79,7 @@
|
|
82
79
|
dt Description
|
83
80
|
dd= simple_format consult.description
|
84
81
|
td
|
82
|
+
/ When printing we just show the notes on the second row
|
83
|
+
tr.print-only.child-row
|
84
|
+
td(colspan=12)= simple_format consult.description
|
85
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
table
|
2
|
+
thead
|
3
|
+
tr
|
4
|
+
th.col-width-large PATIENT
|
5
|
+
th.col-width-reference-no HOSP NOs.
|
6
|
+
th.col-width-large LOCATION
|
7
|
+
th MODALITY
|
8
|
+
th.col-width-tiny SEX
|
9
|
+
th.col-width-date DOB
|
10
|
+
|
11
|
+
- consults.each do |consult|
|
12
|
+
tr
|
13
|
+
/ Nested tables. Yum. But necessary to get wkhtml2pdf to avoid orphaning the
|
14
|
+
/ second 'notes' row (using css - see class definition) from the first 'patient details' row.
|
15
|
+
/ When having just one table with 2 rows per consult, wkhtml2pdf sometimes breaks a page
|
16
|
+
/ between the 2 rows when we really want to keep them together. Using a table like this and
|
17
|
+
/ applying page-break-inside: avoid to it we can avoid this problem. The downside is that the
|
18
|
+
/ columns may not always line up exactly if the content is wide.
|
19
|
+
table.nested-table-in-order-to-prevent-orphaned-rows.spacy
|
20
|
+
tbody
|
21
|
+
tr.consult-details
|
22
|
+
td.col-width-large
|
23
|
+
b= consult.patient.to_s(:default)
|
24
|
+
td.col-width-reference-no= consult.patient_hospital_identifiers&.to_s_multiline
|
25
|
+
td.col-width-large
|
26
|
+
- unit = consult.hospital_ward&.hospital_unit
|
27
|
+
= [unit&.unit_code,
|
28
|
+
consult&.hospital_ward,
|
29
|
+
consult.other_site_or_ward].compact.join(" / ")
|
30
|
+
td= consult.patient_current_modality
|
31
|
+
td.col-width-tiny= consult.patient_sex
|
32
|
+
td.col-width-date= l(consult.patient.born_on)
|
33
|
+
|
34
|
+
tr.consult-notes
|
35
|
+
td(colspan=6)= simple_format consult.description
|
@@ -2,8 +2,15 @@
|
|
2
2
|
|
3
3
|
= content_for(:actions) do
|
4
4
|
= link_to("Add", new_admissions_consult_path, class: :button)
|
5
|
+
= link_to(admissions_consults_path(q: params[:q]&.permit!, format: :pdf), class: "button secondary") do
|
6
|
+
i.fa.fa-print
|
7
|
+
| Print (PDF)
|
8
|
+
/data: { target: admissions_consults_path(format: :pdf)
|
5
9
|
|
6
10
|
= within_admin_layout(title: "Admission Consults") do
|
11
|
+
= render "renalware/shared/please_print_using_print_button_warning"
|
12
|
+
= render "renalware/admissions/requests/modal_dialog_placeholder"
|
13
|
+
|
7
14
|
.panel.radius.compact
|
8
15
|
| If the patient is not on Renalware yet, please
|
9
16
|
= link_to("add them", new_patient_path)
|
@@ -18,7 +18,9 @@ tr.hd-session-row(class=[session.state, stripe_class])
|
|
18
18
|
td(rowspan=2)= session.machine_no
|
19
19
|
td(rowspan=2)= session.machine_ktv
|
20
20
|
td(rowspan=2)= session.machine_urr
|
21
|
+
td.print-only(rowspan=2)
|
21
22
|
td.print-only(rowspan=2)= session.notes
|
23
|
+
td.print-only
|
22
24
|
|
23
25
|
tr(class=[session.state, stripe_class])
|
24
26
|
td= tooltip(label: session.summarised_access_used, content: session.access_used)
|
@@ -31,3 +33,4 @@ tr(class=[session.state, stripe_class])
|
|
31
33
|
td= session.after_measurement_for(:temperature)
|
32
34
|
td= session.after_measurement_for(:bm_stix)
|
33
35
|
td= session.after_measurement_for(:blood_pressure)
|
36
|
+
td.print-only
|
@@ -1,6 +1,6 @@
|
|
1
1
|
.document-view
|
2
|
-
.
|
3
|
-
.
|
2
|
+
.grid
|
3
|
+
.row
|
4
4
|
= render "renalware/shared/attributes_group",
|
5
5
|
legend: "Sign-In", destination: "signin",
|
6
6
|
models: { session => [:performed_on,
|
@@ -28,7 +28,6 @@
|
|
28
28
|
group: session.document.observations_after,
|
29
29
|
legend: "Post-Dialysis Observations", destination: "after"
|
30
30
|
|
31
|
-
.columns.large-6
|
32
31
|
- %W(Dialysis HDF).each do |group|
|
33
32
|
= render "renalware/shared/documents/attributes_group",
|
34
33
|
group: session.document.public_send(group.parameterize(separator: "_")),
|
@@ -39,6 +38,6 @@
|
|
39
38
|
legend: "Notes/Complications", destination: "complications",
|
40
39
|
models: { session.document.complications => attributes_list,
|
41
40
|
session => [:notes] }
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
.row
|
42
|
+
.columns
|
43
|
+
= render "prescription_administrations", session: session
|
@@ -18,6 +18,8 @@ thead
|
|
18
18
|
th.col-width-tiny.text-center(colspan=3)= t(".machine")
|
19
19
|
th.col-width-drugs.text-center.print-only(rowspan=3)= t(".hd_drugs_administered")
|
20
20
|
th.col-width-notes.text-center.print-only(rowspan=3)= t(".notes")
|
21
|
+
th.col-width-user.text-center.print-only= t(".user")
|
22
|
+
|
21
23
|
|
22
24
|
tr.row2
|
23
25
|
th(rowspan=2)= t(".access")
|
@@ -30,6 +32,7 @@ thead
|
|
30
32
|
th.col-width-minute.col-width-tiny(rowspan=2)=t(".machine_no")
|
31
33
|
th.col-width-minute.col-width-tiny(rowspan=2)=t(".machine_ktv")
|
32
34
|
th.col-width-minute.col-width-tiny(rowspan=2)=t(".machine_urr")
|
35
|
+
th.print-only= t(".put_on_by")
|
33
36
|
tr.row2
|
34
37
|
th.col-width-time= t(".end_time")
|
35
38
|
th= t(".post_with_changed")
|
@@ -37,3 +40,4 @@ thead
|
|
37
40
|
th= t(".post")
|
38
41
|
th= t(".post")
|
39
42
|
th= t(".post")
|
43
|
+
th.print-only= t(".taken_off_by")
|
@@ -15,7 +15,7 @@
|
|
15
15
|
remote: true,
|
16
16
|
wrapper: :horizontal_form) do |f|
|
17
17
|
|
18
|
-
= f.input :default_cc, as: :inline_radio_buttons
|
18
|
+
= f.input :default_cc, as: :inline_radio_buttons, readonly: nil
|
19
19
|
= f.input :description_id, collection: contact_descriptions
|
20
20
|
= f.input :other_description
|
21
21
|
= f.input :notes, as: :text
|
@@ -0,0 +1,25 @@
|
|
1
|
+
// Compile a summary of filters applied. Leave out anything not set.
|
2
|
+
- filters = params.fetch(:q, {}).reject{ |key, value| value.blank? }
|
3
|
+
|
4
|
+
.filter-summary
|
5
|
+
table
|
6
|
+
tr
|
7
|
+
th Filters
|
8
|
+
- if filters.empty?
|
9
|
+
td None
|
10
|
+
- else
|
11
|
+
- if filters[:term].present?
|
12
|
+
th Query:
|
13
|
+
td= filters[:term]
|
14
|
+
- if filters[:on_hotlist].present?
|
15
|
+
th On Hotlist:
|
16
|
+
td= yes_no(filters[:on_hotlist] == "true")
|
17
|
+
- if filters[:action].present?
|
18
|
+
th Action:
|
19
|
+
td= Renalware::Renal::AKIAlertAction.find(filters[:action])&.to_s
|
20
|
+
- if filters[:hospital_unit_id].present?
|
21
|
+
th Site:
|
22
|
+
td= Renalware::Hospitals::Unit.find_by(id: filters[:hospital_unit_id])
|
23
|
+
- if filters[:hospital_ward_id].present?
|
24
|
+
th Ward:
|
25
|
+
td= Renalware::Hospitals::Ward.find_by(id: filters[:hospital_ward_id])
|
@@ -0,0 +1,29 @@
|
|
1
|
+
table
|
2
|
+
thead
|
3
|
+
tr
|
4
|
+
th.col-width-large PATIENT
|
5
|
+
th.col-width-reference-no HOSP NOs.
|
6
|
+
th.col-width-medium WARD
|
7
|
+
th MODALITY
|
8
|
+
th.col-width-tiny CRE
|
9
|
+
th.col-width-date CRE DATE
|
10
|
+
th.col-width-tiny SEX
|
11
|
+
th.col-width-date DOB
|
12
|
+
|
13
|
+
- alerts.each do |alert|
|
14
|
+
tr
|
15
|
+
table.nested-table-in-order-to-prevent-orphaned-rows.spacy
|
16
|
+
tbody
|
17
|
+
tr.alert-details
|
18
|
+
td.col-width-large
|
19
|
+
b= alert.patient.to_s(:default)
|
20
|
+
td.col-width-reference-no= alert.patient&.hospital_identifiers&.to_s_multiline
|
21
|
+
td.col-width-medium= alert.hospital_ward
|
22
|
+
td= alert.patient&.current_modality
|
23
|
+
td.col-width-tiny= alert.max_cre
|
24
|
+
td.col-width-date= I18n.l(alert.cre_date)
|
25
|
+
td.col-width-tiny= alert.patient&.sex
|
26
|
+
td.col-width-date= l(alert.patient&.born_on)
|
27
|
+
|
28
|
+
tr.alert-notes
|
29
|
+
td(colspan=8)= simple_format alert.notes
|
@@ -7,7 +7,7 @@
|
|
7
7
|
as: :grouped_select,
|
8
8
|
group_method: :wards,
|
9
9
|
collection: Renalware::Hospitals::Unit.includes(:wards).ordered,
|
10
|
-
label_method:
|
10
|
+
label_method: ->(ward){ [ward.name, ward.code].compact.join(" - ") },
|
11
11
|
wrapper: :horizontal_medium
|
12
12
|
= f.input :hotlist, wrapper: :horizontal_small, as: :inline_radio_buttons
|
13
13
|
= f.input :max_cre, wrapper: :horizontal_small
|
@@ -1,4 +1,14 @@
|
|
1
|
+
= content_for(:actions) do
|
2
|
+
/ Make sure when generating the PDF we pass in exactly the same params eg q and named_filter
|
3
|
+
/ An alternative to what we have used here is to use
|
4
|
+
/ url_for(request.filtered_parameters&.merge({format: :pdf})
|
5
|
+
/ however this seems to loose the ransack sort option in params[:q][:s]
|
6
|
+
= link_to(renal_filtered_aki_alerts_path(q: params[:q]&.permit!, format: :pdf), class: "button secondary") do
|
7
|
+
i.fa.fa-print
|
8
|
+
| Print (PDF)
|
9
|
+
|
1
10
|
= within_admin_layout(title: "AKI Alerts") do
|
11
|
+
= render "renalware/shared/please_print_using_print_button_warning"
|
2
12
|
= render "filters", form: form, path_params: path_params
|
3
13
|
= render "table", alerts: alerts, search: search
|
4
14
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
- content_for(:head) do
|
2
|
+
= wicked_pdf_stylesheet_link_tag "renalware/table_pdf"
|
3
|
+
|
4
|
+
.pdf
|
5
|
+
.header
|
6
|
+
h1 AKI Alerts
|
7
|
+
- if params[:named_filter].present?
|
8
|
+
h1.muted
|
9
|
+
| /
|
10
|
+
= params[:named_filter]&.humanize
|
11
|
+
h2.muted= I18n.l(Time.zone.today)
|
12
|
+
= render "filters"
|
13
|
+
= render "table", alerts: alerts
|
14
|
+
|