Skip to content

Commit 05cc1a2

Browse files
committed
Add NHS::ImmunisationsAPI.update
Jira-Issue: MAV-1012
1 parent f060c69 commit 05cc1a2

4 files changed

Lines changed: 315 additions & 0 deletions

File tree

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ terraform/.terraform
4040
*.tfstate.backup
4141
scratchpad
4242
spec/fixtures/fhir/immunisation-create.json
43+
spec/fixtures/fhir/immunisation-update.json

app/lib/nhs/immunisations_api.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,64 @@ def record_immunisation(vaccination_record)
4141
end
4242
end
4343

44+
def update_immunisation(vaccination_record)
45+
unless Flipper.enabled?(:immunisations_fhir_api_integration)
46+
Rails.logger.info(
47+
"Not updating vaccination record to immunisations API as the" \
48+
" feature flag is disabled: #{vaccination_record.id}"
49+
)
50+
return
51+
end
52+
53+
if vaccination_record.nhs_immunisations_api_id.blank?
54+
raise "Vaccination record #{vaccination_record.id} missing NHS Immunisation ID"
55+
end
56+
57+
if vaccination_record.nhs_immunisations_api_etag.blank?
58+
raise "Vaccination record #{vaccination_record.id} missing ETag"
59+
end
60+
61+
nhs_id = vaccination_record.nhs_immunisations_api_id
62+
response =
63+
NHS::API.connection.put(
64+
"/immunisation-fhir-api/FHIR/R4/Immunization/#{nhs_id}",
65+
vaccination_record.fhir_record.to_json,
66+
{
67+
"Content-Type" => "application/fhir+json",
68+
"E-Tag" => vaccination_record.nhs_immunisations_api_etag
69+
}
70+
)
71+
72+
if response.status == 200
73+
vaccination_record.update!(
74+
nhs_immunisations_api_synced_at: Time.current,
75+
# This simplistic approach is based on the assumption that the NHS
76+
# Immunisations API will always simply increment the ETag. Alternative
77+
# approaches are to perform a GET after this POST to get the ETag, or
78+
# to update our record before we do this POST, which would mean we
79+
# don't need to store the ETag at all.
80+
#
81+
# However at this time it's our understanding that the API will always
82+
# increment the ETag, and, practically speaking, it's very unlikely
83+
# that our record would be updated by someone else. Hence this simple
84+
# approach.
85+
nhs_immunisations_api_etag:
86+
vaccination_record.nhs_immunisations_api_etag.to_i + 1
87+
)
88+
else
89+
raise "Error updating vaccination record #{vaccination_record.id} to" \
90+
" Immunisations API: unexpected response status" \
91+
" #{response.status}"
92+
end
93+
rescue Faraday::ClientError => e
94+
if (diagnostics = extract_error_diagnostics(e&.response)).present?
95+
raise "Error updating vaccination record #{vaccination_record.id} to" \
96+
" Immunisations API: #{diagnostics}"
97+
else
98+
raise
99+
end
100+
end
101+
44102
private
45103

46104
def extract_error_diagnostics(response)
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{
2+
"id": "ffff1111-eeee-2222-dddd-3333eeee4444",
3+
"contained": [
4+
{
5+
"id": "Practitioner1",
6+
"name": [
7+
{
8+
"family": "Nightingale",
9+
"given": [
10+
"Florence"
11+
]
12+
}
13+
],
14+
"resourceType": "Practitioner"
15+
},
16+
{
17+
"id": "Patient1",
18+
"identifier": [
19+
{
20+
"system": "https://fhir.nhs.uk/Id/nhs-number",
21+
"value": "9449310475"
22+
}
23+
],
24+
"name": [
25+
{
26+
"family": "Taylor",
27+
"given": [
28+
"Sarah"
29+
]
30+
}
31+
],
32+
"gender": "unknown",
33+
"birthDate": "2011-09-09",
34+
"address": [
35+
{
36+
"postalCode": "EC1A 1BB"
37+
}
38+
],
39+
"resourceType": "Patient"
40+
}
41+
],
42+
"extension": [
43+
{
44+
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-VaccinationProcedure",
45+
"valueCodeableConcept": {
46+
"coding": [
47+
{
48+
"system": "http://snomed.info/sct",
49+
"code": "761841000",
50+
"display": "Administration of vaccine product containing only Human papillomavirus antigen (procedure)"
51+
}
52+
]
53+
}
54+
}
55+
],
56+
"identifier": [
57+
{
58+
"system": "http://manage-vaccinations-in-schools.nhs.uk/vaccination_records",
59+
"value": "11112222-3333-4444-5555-666677778888"
60+
}
61+
],
62+
"status": "completed",
63+
"vaccineCode": {
64+
"coding": [
65+
{
66+
"system": "http://snomed.info/sct",
67+
"code": "10880211000001104",
68+
"display": "Gardasil vaccine suspension for injection 0.5ml pre-filled syringes (Merck Sharp & Dohme (UK) Ltd) (product)"
69+
}
70+
]
71+
},
72+
"patient": {
73+
"reference": "#Patient1"
74+
},
75+
"occurrenceDateTime": "2021-02-07T13:28:17.271+00:00",
76+
"recorded": "2021-02-07T13:28:17.271+00:00",
77+
"primarySource": true,
78+
"location": {
79+
"identifier": {
80+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
81+
"value": "X99999"
82+
}
83+
},
84+
"manufacturer": {
85+
"display": "Merck Sharp & Dohme"
86+
},
87+
"lotNumber": "X8U375AL",
88+
"expirationDate": "2023-03-20",
89+
"site": {
90+
"coding": [
91+
{
92+
"system": "http://snomed.info/sct",
93+
"code": "368208006",
94+
"display": "Left upper arm structure"
95+
}
96+
]
97+
},
98+
"route": {
99+
"coding": [
100+
{
101+
"system": "http://snomed.info/sct",
102+
"code": "78421000",
103+
"display": "Intramuscular"
104+
}
105+
]
106+
},
107+
"doseQuantity": {
108+
"value": 0.5,
109+
"unit": "ml",
110+
"system": "http://snomed.info/sct",
111+
"code": "258773002"
112+
},
113+
"performer": [
114+
{
115+
"actor": {
116+
"reference": "#Practitioner1"
117+
}
118+
},
119+
{
120+
"actor": {
121+
"type": "Organization",
122+
"identifier": {
123+
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
124+
"value": "A9A5A"
125+
}
126+
}
127+
}
128+
],
129+
"reasonCode": [
130+
{
131+
"coding": [
132+
{
133+
"system": "http://snomed.info/sct",
134+
"code": "723620004"
135+
}
136+
]
137+
}
138+
],
139+
"protocolApplied": [
140+
{
141+
"targetDisease": [
142+
{
143+
"coding": [
144+
{
145+
"system": "http://snomed.info/sct",
146+
"code": "240532009",
147+
"display": "Human papillomavirus infection"
148+
}
149+
]
150+
}
151+
],
152+
"doseNumberPositiveInt": 1
153+
}
154+
],
155+
"resourceType": "Immunization"
156+
}

spec/lib/nhs/immunisations_api_spec.rb

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,104 @@
206206

207207
include_examples "an immunisations_fhir_api_integration feature flag check"
208208
end
209+
210+
describe "update immunisations" do
211+
subject(:perform_request) do
212+
described_class.update_immunisation(vaccination_record)
213+
end
214+
215+
let(:status) { 200 }
216+
let(:body) { "" }
217+
let!(:request_stub) do
218+
stub_request(
219+
:put,
220+
"https://sandbox.api.service.nhs.uk/immunisation-fhir-api/FHIR/R4/Immunization/ffff1111-eeee-2222-dddd-3333eeee4444"
221+
).to_return(status:, body:)
222+
end
223+
224+
before do
225+
vaccination_record.update(
226+
nhs_immunisations_api_id: "ffff1111-eeee-2222-dddd-3333eeee4444",
227+
nhs_immunisations_api_synced_at: Date.yesterday,
228+
nhs_immunisations_api_etag: 1
229+
)
230+
end
231+
232+
it "sends the correct JSON payload" do
233+
expected_body =
234+
File.read(
235+
Rails.root.join("spec/fixtures/fhir/immunisation-update.json")
236+
).chomp
237+
238+
request_stub.with do |request|
239+
expect(request.headers).to include(
240+
{
241+
"Accept" => "application/fhir+json",
242+
"Content-Type" => "application/fhir+json",
243+
"E-Tag" => "1"
244+
}
245+
)
246+
expect(request.body).to eq expected_body
247+
true
248+
end
249+
250+
perform_request
251+
252+
expect(request_stub).to have_been_made
253+
end
254+
255+
include_examples "an immunisations_fhir_api_integration feature flag check"
256+
257+
it "sets the nhs_immunisations_api_synced_at" do
258+
freeze_time do
259+
perform_request
260+
261+
expect(
262+
vaccination_record.nhs_immunisations_api_synced_at
263+
).to eq Time.current
264+
end
265+
end
266+
267+
it "increments the etag" do
268+
perform_request
269+
270+
expect(vaccination_record.nhs_immunisations_api_etag).to eq "2"
271+
end
272+
273+
context "an error is returned by the api" do
274+
let(:code) { nil }
275+
let(:diagnostics) { nil }
276+
277+
let(:body) do
278+
{
279+
resourceType: "OperationOutcome",
280+
id: "bc2c3c82-4392-4314-9d6b-a7345f82d923",
281+
meta: {
282+
profile: [
283+
"https://simplifier.net/guide/UKCoreDevelopment2/ProfileUKCore-OperationOutcome"
284+
]
285+
},
286+
issue: [
287+
{
288+
severity: "error",
289+
code: "invalid",
290+
details: {
291+
coding: [
292+
{
293+
system: "https://fhir.nhs.uk/Codesystem/http-error-codes",
294+
code:
295+
}
296+
]
297+
},
298+
diagnostics:
299+
}
300+
]
301+
}.to_json
302+
end
303+
304+
include_examples "unexpected response status", 201, "updating"
305+
include_examples "client error (4XX) handling", "updating"
306+
include_examples "generic error handling"
307+
end
308+
end
209309
end

0 commit comments

Comments
 (0)