# frozen_string_literal: true
require_dependency "renalware/ukrdc"
require "attr_extras"
module Renalware
module UKRDC
class CreatePatientXMLFile
pattr_initialize [
:patient!,
:dir!,
:request_uuid!,
:changes_since,
:logger,
:batch_number,
:renderer, # so we can pass a test renderer to bypass real rendering
:log,
:force_send
]
# rubocop:disable Metrics/AbcSize
# If force_send is true then send all files even if they have not changed since the last
# send. This is primarily for debugging and testing phases with UKRDC
def call
update_patient_to_indicated_we_checked_them_for_any_relevant_changes
UKRDC::TransmissionLog.with_logging(patient, request_uuid) do |log|
@log = log
logger.info " Patient #{patient.ukrdc_external_id}"
xml_payload = build_payload(log)
if xml_payload.present?
if !force_send && xml_payload_same_as_last_sent_payload?(xml_payload)
logger.info " skipping as no change in XML file"
log.unsent_no_change_since_last_send!
else
create_xml_file(xml_payload, log)
update_patient_to_indicate_we_have_sent_their_data_to_ukrdc
end
end
logger.info " Status: #{log.status}"
end
end
# rubocop:enable Metrics/AbcSize
private
def logger
@logger ||= Rails.logger
end
# Important we use update_column here so we don't trigger updated_at to change
# on the patient, which affects the results of PatientsQuery next time.
def update_patient_to_indicated_we_checked_them_for_any_relevant_changes
patient.update_column(:checked_for_ukrdc_changes_at, Time.zone.now)
end
# Important we use update_column here so we don't trigger updated_at to change
# on the patient, which affects the results of PatientsQuery next time.
def update_patient_to_indicate_we_have_sent_their_data_to_ukrdc
patient.update_column(:sent_to_ukrdc_at, Time.zone.now)
logger.info(
" sending file and setting patient.sent_to_ukrdc_at = #{patient.sent_to_ukrdc_at}"
)
end
def xml_payload_same_as_last_sent_payload?(payload)
payload.to_md5_hash == last_sent_transmission_log.payload_hash
end
def create_xml_file(payload, log)
File.open(xml_filepath, "w") { |file| file.write(payload) }
log.file_path = xml_filepath
log.sent!
end
def last_sent_transmission_log
@last_sent_transmission_log ||= begin
TransmissionLog.where(patient: patient, status: :sent).ordered.last || NullObject.instance
end
end
def build_payload(log)
result = attempt_to_generate_patient_ukrdc_xml
if result.failure?
handle_invalid_xml(result)
nil
else
Payload.new(result.xml).tap do |payload|
log.payload = payload.to_s
log.payload_hash = payload.to_md5_hash
end
end
end
def attempt_to_generate_patient_ukrdc_xml
(renderer || default_renderer).call
end
def handle_invalid_xml(result)
log.error = result.validation_errors
log.status = :error
nil
end
def default_renderer
Renalware::UKRDC::XmlRenderer.new(locals: { patient: presenter_for(patient) })
end
def presenter_for(patient)
Renalware::UKRDC::PatientPresenter.new(
patient,
changes_since: changes_since
)
end
def xml_filepath
xml_filename = Filename.new(patient: patient, batch_number: batch_number).to_s
File.join(dir, xml_filename)
end
class Payload
pattr_initialize :payload
delegate :to_s, to: :payload
def to_md5_hash
@to_md5_hash ||= Digest::MD5.hexdigest(time_neutral_payload)
end
# Remove the time elements from SendingFacility
# e.g.
#
# becomes
#
# This allows us to do payload comparisons independent of the time they were sent.
def time_neutral_payload
payload
.gsub(%r{[^<]*<\/Stream>}, "removed")
.gsub(/ (time|start|stop)=["'][^'"]*['"]/, "")
.gsub(%r{[^<]*<\/UpdatedOn>}, "removed")
end
end
end
end
end