package Reporter::Model; use Reporter::Validator; # Data::FormValidator class use Reporter::Class; # provides Moo, Clone, Modern::Perl, Local::MooX::Types & Data::Printer::p use Reporter::DB; has dbname => ( is => 'ro', isa => String, required => 1 ); # hilis4, test, etc has config => ( is => 'ro', isa => HashReference, required => 1 ); # app config has cache => ( is => 'rw', isa => HashReference, default => sub { {} } ); has db => ( is => 'lazy', handles => [ 'check_db_connection' ], # to check db handle still connected ); sub _build_db { my $self = shift; Reporter::DB->new( dbname => $self->dbname, config => $self->config ); } has validator => ( # Data::FormValidator is => 'lazy', builder => sub { Reporter::Validator->new }, ); use IO::All; use Digest::SHA1; use File::Temp qw(tempfile); use DateTime::Format::MySQL; use Local::Paginator qw(paginate); use Email::Sender::Simple qw(sendmail); # BEGIN { $ENV{EMAIL_SENDER_TRANSPORT} = 'Test' } # to force test email delivery # or to 'TestFail' to cause it to fail # use Lingua::EN::Numbers qw(num2en_ordinal); #------------------------------------------------------------------------------- sub validate_user_credentials { my ($self, $params) = @_; # p $params; my $userid = $params->{userid}; # username or email addr my $passwd = $params->{password}; if ( my $user = $self->db->get_user_data($userid) ) { # p $user; return $user if $self->config->{environment} eq 'development'; # don't need pwd my $digest = _sha1_digest($passwd); # p $digest; return $user if $user->{password} eq $digest; } # either no user retrieved by username or passwd, or passwd incorrect: return 0; } #------------------------------------------------------------------------------- sub register_user { my ($self, $params) = @_; # p $params; my $result = do { # clone $params, replace password value with sha1_digest: my $ref = clone $params; $ref->{password} = _sha1_digest($params->{password}); # p $ref; $self->db->register_user($ref); }; return $result; } #------------------------------------------------------------------------------- sub email_registration { my ($self, $params) = @_; # p $data; my $app_config = $self->config; my $transport = do { # TestFail = my Email::Sender::Transport::TestFail package below my $t = $ENV{EMAIL_SENDER_TRANSPORT} # command-line ENV has priority || $params->{email} =~ /fail/ # then web-form 'email' param =~ /fail/ ? 'TestFail' # then .yml setting, finally default smtp: : $app_config->{email_sender_transport} || 'SMTP'; my $transport_module = 'Email::Sender::Transport::'.$t; use_module($transport_module)->new; # p $transport_module; }; # ddp $transport; # Email::Sender::Transport object my $content = _format_message($params); my $email = Email::Simple->create( body => $content, header => [ to => $params->{email}, cc => $app_config->{admin_email}, from => $app_config->{email_from}, 'reply-to' => $app_config->{admin_email}, subject => ( 'HMDS draft reporter registration' ), ], ); # ddp $email->as_string; ddp $email->header_pairs; my %h = (); try { sendmail($email, { transport => $transport } ); } catch { $h{error} = 'email failed: '.$_->message; } finally { # attach message to tt if test script: $h{email_as_string} = $email->as_string if $ENV{EMAIL_SENDER_TRANSPORT} =~ /Test/; }; # p %h; return \%h; } sub _format_message { my $params = shift; return sprintf qq! Hello %s %s, Your HMDS draft reporter registration details: username: %s password: %s Address: !, ucfirst($params->{first_name}), ucfirst($params->{last_name}), @{$params}{qw/email password/}; } #------------------------------------------------------------------------------- sub load_request { my ($self, $args) = @_; # p $args; my ($lab_number, $user_id) = map $args->{$_}, qw(lab_number user_id); # split lab_number into request_number & yr: my ($request_number, $yr) = $lab_number =~ m!(\d+)/(\d{2})!; # request data: my $request = $self->db->get_request_data($request_number, $yr); # p $request; my $request_id = $request->{id} || return 0; # in case request not found { # section result summaries: my $data = $self->db->get_result_summaries($request_id); # p $data; $request->{result_summaries} = $data; } { # lab test results: my $data = $self->db->get_lab_test_results($request_id); # p $data; $request->{test_results} = $data; } { # previous cases: my $nhs_number = $request->{nhs_number}; # will return empty if undef: my $previous = $self->db->get_previous_requests($request_id, $nhs_number); if ($previous) { # p $previous; $_->{auth_date} = $self->to_datetime($_->{auth_date}) for @$previous; $request->{previous_requests} = $previous; } } { # previous draft report by same user: my $data = $self->db->get_report_data($request_id, $user_id); # p $data; $data->{$_} = $self->to_datetime($data->{$_}) for qw(created_at updated_at); $request->{report} = $data; } # dates to datetime: $request->{$_} = $self->to_datetime($request->{$_}) for qw(registered dob); # store for later use: $self->cache( { request_data => $request } ); return $request; } #------------------------------------------------------------------------------- sub get_validation_profile { my ($self, $profile_name) = @_; # p $profile_name; # clone DFV profile otherwise changes affect original profiles hashref: my $profile = clone $self->validator->profiles->{$profile_name}; # p $profile; if ( $profile_name eq 'report' ) { # possibly add new required fields: my $required_fields = $profile->{required}; # p $required_fields; # arrayref my $request_data = $self->cache->{request_data}; # p $request_data; my $specimen = $request_data->{specimen}; my %re = ( fixed_or_trephine => qr{BMAT|[DGLRXT]([BS][LP]|F|U)}, histology_type => qr{[DGLRX]([BS]L|F|U)}, ); push @$required_fields, 'biopsy_site' if $specimen =~ /$re{histology_type}/; push @$required_fields, 'gross_description' if $specimen =~ /$re{fixed_or_trephine}/; } # p $profile; return $profile; } #------------------------------------------------------------------------------- sub get_reports_for_user { my ($self, $args) = @_; # p $args; my $user_id = $args->{user_id}; my $config = $self->config->{paginator}; # p $config; my $page = $args->{page}; my $uri = $args->{uri}; my $limit = $config->{page_item_count}; # p $limit; my $total = $self->db->get_report_counts_for_user($user_id); # p $total; # get list of reported requests for this user: my $data = do { my %args = ( limit => $limit, offset => $limit * ($page - 1), ); $self->db->get_reports_for_user($user_id, \%args); }; # p $data; # registration date to datetime: $_->{created_at} = $self->to_datetime($_->{created_at}) for @$data; my %h = ( reports => $data ); if ( $total > $limit ) { # need pagination: my %args = ( entries_per_page => $limit, total_entries => $total, current_page => $page, sibs => $config->{siblings}, uri => $uri . '?page=', ); # p %args; my $paginator = paginate(\%args); # adds formatted html to %args $h{paginator} = $paginator; } return \%h; } #------------------------------------------------------------------------------- sub user_reports_list { my ($self, $user_id) = @_; # p $user_id; my $data = $self->db->get_reports_for_user($user_id); # p $data; $_->{created_at} = $self->to_datetime($_->{created_at}) for @$data; my %specimen; # specimen type => count map: $specimen{ $_->{specimen} }++ for @$data; # p %specimen; # sort by frequency in descending order: my @specimens = map { { id => $_, n => $specimen{$_} }; # frequency then alphabetically: } sort { $specimen{$b} <=> $specimen{$a} || $a cmp $b } keys %specimen; my %h = ( reports => $data, specimen => \@specimens, ); return \%h; } #------------------------------------------------------------------------------- sub render_pdf { my ($self, $content) = @_; # p $content; # create temp file for input to wkhtmltopdf (default is to UNLINK): my $tmp_file = File::Temp->new(SUFFIX => '.html', UNLINK => 1); # p $tmp_file; io($tmp_file->filename)->print($content); # save file to disk my @args = ( '--header-spacing 5', '--footer-spacing 5', '--header-font-size 9', '--footer-font-size 9', qq!--header-right 'Printed on: [date]'!, qq!--footer-center 'Page [page] of [toPage]'!, ); my $pdf = `/usr/local/bin/wkhtmltopdf -q @args $tmp_file -`; return $pdf; } #------------------------------------------------------------------------------- sub save_report { my ($self, $data) = @_; # p $data; my $result = $self->db->save_report($data); # p $result; { # construct msg for tt (displayed if no error from insert/update): my $action = $result->{action}; my %msgs = ( create_record => 'new record created successfully', update_record => 'record updated successfully', ); # p $msgs{$action}; $result->{message} = $msgs{$action} || 'unknown action'; } return $result; # hashref of keys success, message & optional error } #------------------------------------------------------------------------------- sub get_diagnoses { my ($self, $str) = @_; # p $str; return 0 unless length $str >= 3; # causes db error if undef my $data = $self->db->get_diagnoses($str); # p $data; return $data; } #------------------------------------------------------------------------------- sub get_lab_number { shift->db->get_lab_number(@_) } #------------------------------------------------------------------------------- sub num2ordinal { num2en_ordinal($_[1]) } #------------------------------------------------------------------------------- sub to_datetime { my $self = shift; my $date = shift || return; return $date =~ /\d{2}:\d{2}:\d{2}$/ # time ? DateTime::Format::MySQL->parse_datetime($date) : DateTime::Format::MySQL->parse_date($date); } #------------------------------------------------------------------------------- sub _sha1_digest { my $str = shift; # warn $str; my $sha1 = Digest::SHA1->new; $sha1->add($str); return $sha1->b64digest; } { package Email::Sender::Transport::TestFail; use Data::Printer; use Moo; extends 'Email::Sender::Transport::Test'; sub delivery_failure { my ($self, $email) = @_; # ddp $email; return Email::Sender::Failure->new('test delivery failure'); } no Moo; 1; } 1;