Bug 34587: Abstract reports backend to allow new data types
[koha.git] / Koha / ERM / UsageDataProvider.pm
1 package Koha::ERM::UsageDataProvider;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use HTTP::Request;
21 use JSON qw( decode_json );
22 use LWP::UserAgent;
23 use Text::CSV_XS qw( csv );
24
25 use Koha::Exceptions;
26
27 use base qw(Koha::Object);
28
29 use Koha::ERM::CounterFile;
30 use Koha::ERM::CounterFiles;
31 use Koha::ERM::UsageTitles;
32 use Koha::ERM::UsageItems;
33 use Koha::ERM::UsagePlatforms;
34 use Koha::ERM::UsageDatabases;
35 use Koha::ERM::MonthlyUsages;
36 use Koha::BackgroundJob::ErmSushiHarvester;
37
38 =head1 NAME
39
40 Koha::ERM::UsageDataProvider - Koha ErmUsageDataProvider Object class
41
42 =head1 API
43
44 =head2 Class Methods
45
46 =head3 counter_files
47
48 Getter/setter for counter_files for this usage data provider
49
50 =cut
51
52 sub counter_files {
53     my ( $self, $counter_files ) = @_;
54
55     if ($counter_files) {
56         for my $counter_file (@$counter_files) {
57             Koha::ERM::CounterFile->new($counter_file)
58               ->store( $self->{job_callbacks} );
59         }
60     }
61     my $counter_files_rs = $self->_result->erm_counter_files;
62     return Koha::ERM::CounterFiles->_new_from_dbic($counter_files_rs);
63 }
64
65 =head3 enqueue_counter_file_processing_job
66
67 Enqueues a background job to process a COUNTER file that has been uploaded
68
69 =cut
70
71 sub enqueue_counter_file_processing_job {
72     my ( $self, $args ) = @_;
73
74     my @jobs;
75     my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue(
76         {
77             ud_provider_id => $self->erm_usage_data_provider_id,
78             file_content   => $args->{file_content},
79         }
80     );
81
82     push(
83         @jobs,
84         {
85             job_id => $job_id
86         }
87     );
88
89     return \@jobs;
90 }
91
92 =head3 enqueue_sushi_harvest_jobs
93
94 Enqueues one harvest background job for each report type in this usage data provider
95
96 =cut
97
98 sub enqueue_sushi_harvest_jobs {
99     my ( $self, $args ) = @_;
100
101     my @report_types = split( /;/, $self->report_types );
102
103     my @jobs;
104     foreach my $report_type (@report_types) {
105
106         my $job_id = Koha::BackgroundJob::ErmSushiHarvester->new->enqueue(
107             {
108                 ud_provider_id => $self->erm_usage_data_provider_id,
109                 report_type    => $report_type
110             }
111         );
112
113         push(
114             @jobs,
115             {
116                 report_type => $report_type,
117                 job_id      => $job_id
118             }
119         );
120     }
121
122     return \@jobs;
123 }
124
125 =head3 harvest
126
127     $ud_provider->harvest(
128         {
129             step_callback        => sub { $self->step; },
130             set_size_callback    => sub { $self->set_job_size(@_); },
131             add_message_callback => sub { $self->add_message(@_); },
132         }
133     );
134
135 Run the SUSHI harvester of this usage data provider
136 Builds the URL query and requests the COUNTER 5 SUSHI service
137
138 COUNTER SUSHI api spec:
139 https://app.swaggerhub.com/apis/COUNTER/counter-sushi_5_0_api/5.0.2
140
141 =over
142
143 =item report_type
144
145 Report type to run this harvest on
146
147 =back
148
149 =over
150
151 =item background_job_callbacks
152
153 Receive background_job_callbacks to be able to update job
154
155 =back
156
157 =cut
158
159 sub harvest {
160     my ( $self, $report_type, $background_job_callbacks ) = @_;
161
162     # Set class wide vars
163     $self->{job_callbacks} = $background_job_callbacks;
164     $self->{report_type} = $report_type;
165
166     my $url      = $self->_build_url_query;
167     my $request  = HTTP::Request->new( 'GET' => $url );
168     my $ua       = LWP::UserAgent->new;
169     my $response = $ua->simple_request($request);
170
171     if ( $response->code >= 400 ) {
172         my $result = decode_json( $response->decoded_content );
173
174         my $message;
175         if ( ref($result) eq 'ARRAY' ) {
176             for my $r (@$result) {
177                 $message .= $r->{message};
178             }
179         }
180         else {
181             #TODO: May want to check $result->{Report_Header}->{Exceptions} here
182             $message = $result->{message} || $result->{Message} || q{};
183             if ( $result->{errors} ) {
184                 for my $e ( @{ $result->{errors} } ) {
185                     $message .= $e->{message};
186                 }
187             }
188         }
189
190         #TODO: May want to add a job error message here?
191         warn sprintf "ERROR - SUSHI service %s returned %s - %s\n", $url,
192           $response->code, $message;
193         if ( $response->code == 404 ) {
194             Koha::Exceptions::ObjectNotFound->throw($message);
195         }
196         elsif ( $response->code == 401 ) {
197             Koha::Exceptions::Authorization::Unauthorized->throw($message);
198         }
199         else {
200             #TODO: May want to add a job error message here?
201             die sprintf "ERROR requesting SUSHI service\n%s\ncode %s: %s\n",
202               $url, $response->code,
203               $message;
204         }
205     }
206     elsif ( $response->code == 204 ) {    # No content
207         return;
208     }
209
210     # Parse the SUSHI response
211     $self->parse_SUSHI_response( decode_json( $response->decoded_content ) );
212 }
213
214 =head3 parse_SUSHI_response
215
216     $self->parse_SUSHI_response( decode_json( $response->decoded_content ) );
217
218 Parse the SUSHI response, prepare the COUNTER report file header,
219 column headings and body
220
221 =over
222
223 =item result
224
225 The result of the SUSHI response after json decoded
226
227 =back
228
229 =cut
230
231 sub parse_SUSHI_response {
232     my ( $self, $result ) = @_;
233
234     # Set class wide sushi response content
235     $self->{sushi} = {
236         header => $result->{Report_Header},
237         body   => $result->{Report_Items}
238     };
239
240     #TODO: Handle empty $self->{sushi}->{body} here!
241
242     # Get ready to build COUNTER file
243     my @report_header          = $self->_COUNTER_report_header;
244     my @report_column_headings = $self->_COUNTER_report_column_headings;
245     my @report_body            = $self->_COUNTER_report_body;
246
247     $self->_build_COUNTER_report_file( \@report_header,
248         \@report_column_headings, \@report_body );
249 }
250
251 =head2 Internal methods
252
253 =head3 _build_url_query
254
255 Build the URL query params for COUNTER 5 SUSHI request
256
257 =cut
258
259 sub _build_url_query {
260     my ($self) = @_;
261
262     unless ( $self->service_url && $self->customer_id ) {
263         die sprintf
264 "SUSHI Harvesting config for usage data provider %d is missing service_url or customer_id\n",
265           $self->erm_usage_data_provider_id;
266     }
267
268     # FIXME: service_url needs to end in 'reports/'
269     # below concat will result in a badly formed URL otherwise
270     # Either validate this on UI form, here, or both
271     my $url = $self->service_url;
272
273     $url .= $self->{report_type};
274     $url .= '?customer_id=' . $self->customer_id;
275     $url .= '&requestor_id=' . $self->requestor_id if $self->requestor_id;
276     $url .= '&api_key=' . $self->api_key           if $self->api_key;
277     $url .= '&begin_date=' . $self->begin_date     if $self->begin_date;
278     $url .= '&end_date=' . $self->end_date         if $self->end_date;
279
280     return $url;
281 }
282
283 =head3 _build_COUNTER_report_file
284
285 Build the COUNTER file
286 https://cop5.projectcounter.org/en/5.0.2/03-specifications/02-formats-for-counter-reports.html#report-header
287
288 =cut
289
290 sub _build_COUNTER_report_file {
291     my ( $self, $header, $column_headings, $body ) = @_;
292
293     my @report = ( @{$header}, @{$column_headings}, @{$body} );
294
295     #TODO: change this to tab instead of comma
296     csv( in => \@report, out => \my $counter_file, encoding => "utf-8" );
297
298     $self->counter_files(
299         [
300             {
301                 usage_data_provider_id => $self->erm_usage_data_provider_id,
302                 file_content           => $counter_file,
303                 date_uploaded => POSIX::strftime( "%Y%m%d%H%M%S", localtime ),
304
305                 #TODO: add ".csv" to end of filename here
306                 filename => $self->name . "_" . $self->{report_type},
307             }
308         ]
309     );
310 }
311
312 =head3 _COUNTER_report_header
313
314 Return a COUNTER report header
315 https://cop5.projectcounter.org/en/5.0.2/04-reports/03-title-reports.html
316
317 =cut
318
319 sub _COUNTER_report_header {
320     my ($self) = @_;
321
322     my $header = $self->{sushi}->{header};
323
324     my @metric_types_string =
325       $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "Metric_Type" );
326
327     my $begin_date =
328       $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "Begin_Date" );
329     my $end_date =
330       $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "End_Date" );
331
332     return (
333         [ Report_Name      => $header->{Report_Name}      || "" ],
334         [ Report_ID        => $header->{Report_ID}        || "" ],
335         [ Release          => $header->{Release}          || "" ],
336         [ Institution_Name => $header->{Institution_Name} || "" ],
337         [
338             Institution_ID => join(
339                 "; ",
340                 map( $_->{Type} . ":" . $_->{Value},
341                     @{ $header->{Institution_ID} } )
342               )
343               || ""
344         ],
345         [
346             Metric_Types => join( "; ", split( /\|/, $metric_types_string[0] ) )
347               || ""
348         ],
349         [
350             Report_Filters => join(
351                 "; ",
352                 map( $_->{Name} . ":" . $_->{Value},
353                     @{ $header->{Report_Filters} } )
354               )
355               || ""
356         ],
357
358 #TODO: Report_Attributes may need parsing, test this with a SUSHI response that provides it
359         [ Report_Attributes => $header->{Report_Attributes} || "" ],
360         [
361             Exceptions => join(
362                 "; ",
363                 map( $_->{Code} . ": "
364                       . $_->{Message} . " ("
365                       . $_->{Data} . ")",
366                     @{ $header->{Exceptions} } )
367               )
368               || ""
369         ],
370         [
371                 Reporting_Period => "Begin_Date="
372               . $begin_date
373               . "; End_Date="
374               . $end_date
375         ],
376         [ Created    => $header->{Created}    || "" ],
377         [ Created_By => $header->{Created_By} || "" ],
378         [""]    #empty 13th line
379     );
380 }
381
382 =head3 _COUNTER_item_report_row
383
384 Return a COUNTER item for the COUNTER items report body
385 https://cop5.projectcounter.org/en/5.0.2/04-reports/04-item-reports.html#column-headings-elements
386
387 =cut
388
389 sub _COUNTER_item_report_row {
390     my ( $self, $item_row, $metric_type, $total_usage, $monthly_usages ) = @_;
391
392     return (
393         [
394             $item_row->{Item}      || "",
395             $item_row->{Publisher} || "",
396             $self->_get_SUSHI_Type_Value( $item_row->{Publisher_ID}, "ISNI" )
397               || "",
398             $item_row->{Platform}                                       || "",
399             $self->_get_SUSHI_Type_Value( $item_row->{Item_ID}, "DOI" ) || "",
400             $item_row->{Proprietary_ID}                                 || "",
401             "",    #FIXME: What goes in URI?
402             $metric_type,
403             $total_usage,
404             @{$monthly_usages}
405         ]
406     );
407 }
408
409 =head3 _COUNTER_database_report_row
410
411 Return a COUNTER database for the COUNTER databases report body
412 https://cop5.projectcounter.org/en/5.0.2/04-reports/02-database-reports.html#column-headings-elements
413
414 =cut
415
416 sub _COUNTER_database_report_row {
417     my ( $self, $database_row, $metric_type, $total_usage, $monthly_usages ) =
418       @_;
419
420     return (
421         [
422             $database_row->{Database}  || "",
423             $database_row->{Publisher} || "",
424             $self->_get_SUSHI_Type_Value( $database_row->{Publisher_ID},
425                 "ISNI" )
426               || "",
427             $database_row->{Platform}       || "",
428             $database_row->{Proprietary_ID} || "",
429             $metric_type,
430             $total_usage,
431             @{$monthly_usages}
432         ]
433     );
434 }
435
436 =head3 _COUNTER_platform_report_row
437
438 Return a COUNTER platform for the COUNTER platforms report body
439 https://cop5.projectcounter.org/en/5.0.2/04-reports/01-platform-reports.html#column-headings-elements
440
441 =cut
442
443 sub _COUNTER_platform_report_row {
444     my ( $self, $platform_row, $metric_type, $total_usage, $monthly_usages ) =
445       @_;
446
447     return (
448         [
449             $platform_row->{Platform} || "", $metric_type,
450             $total_usage,                    @{$monthly_usages}
451         ]
452     );
453 }
454
455 =head3 _COUNTER_title_report_row
456
457 Return a COUNTER title for the COUNTER titles report body
458 https://cop5.projectcounter.org/en/5.0.2/04-reports/03-title-reports.html#column-headings-elements
459
460 =cut
461
462 sub _COUNTER_title_report_row {
463     my ( $self, $title_row, $metric_type, $total_usage, $monthly_usages ) = @_;
464
465     my $header = $self->{sushi}->{header};
466     my $specific_fields =
467       $self->get_report_type_specific_fields( $header->{Report_ID} );
468
469     return (
470         [
471             # Title
472             $title_row->{Title} || "",
473
474             # Publisher
475             $title_row->{Publisher} || "",
476
477             # Publisher_ID
478             $self->_get_SUSHI_Type_Value( $title_row->{Publisher_ID}, "ISNI" )
479               || "",
480
481             # Platform
482             $title_row->{Platform} || "",
483
484             # DOI
485             $self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "DOI" ) || "",
486
487             # Proprietary_ID
488             $self->_get_SUSHI_Type_Value(
489                 $title_row->{Item_ID}, "Proprietary"
490               )
491               || "",
492
493             # ISBN
494             grep ( /ISBN/, @{$specific_fields} )
495             ? ( $self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "ISBN" )
496                   || "" )
497             : (),
498
499             # Print_ISSN
500             $self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "Print_ISSN" )
501               || "",
502
503             # Online_ISSN
504             $self->_get_SUSHI_Type_Value(
505                 $title_row->{Item_ID}, "Online_ISSN"
506               )
507               || "",
508
509             # URI - FIXME: What goes in URI?
510             "",
511
512             # YOP
513             grep ( /YOP/, @{$specific_fields} )
514             ? ( $title_row->{YOP} || "" )
515             : (),
516
517             # Access_Type
518             grep ( /Access_Type/, @{$specific_fields} )
519             ? ( $title_row->{Access_Type} || "" )
520             : (),
521
522             # Metric_Type
523             $metric_type,
524
525             # Report_Period_Total
526             $total_usage,
527
528             # Monthly usage entries
529             @{$monthly_usages}
530         ]
531     );
532 }
533
534 =head3 _COUNTER_report_row
535
536 Return a COUNTER row for the COUNTER report body
537
538 =cut
539
540 sub _COUNTER_report_row {
541     my ( $self, $report_row, $metric_type ) = @_;
542
543     my $header = $self->{sushi}->{header};
544
545     my ( $total_usage, @monthly_usages ) =
546       $self->_get_row_usages( $report_row, $metric_type );
547
548     if ( $header->{Report_ID} =~ /PR/i ) {
549         return $self->_COUNTER_platform_report_row( $report_row, $metric_type,
550             $total_usage, \@monthly_usages );
551     }
552     elsif ( $header->{Report_ID} =~ /DR/i ) {
553         return $self->_COUNTER_database_report_row( $report_row, $metric_type,
554             $total_usage, \@monthly_usages );
555     }
556     elsif ( $header->{Report_ID} =~ /IR/i ) {
557         return $self->_COUNTER_item_report_row( $report_row, $metric_type,
558             $total_usage, \@monthly_usages );
559     }
560     elsif ( $header->{Report_ID} =~ /TR/i ) {
561         return $self->_COUNTER_title_report_row( $report_row, $metric_type,
562             $total_usage, \@monthly_usages );
563     }
564 }
565
566 =head3 _get_row_usages
567
568 Returns the total and monthly usages for a row
569
570 =cut
571
572 sub _get_row_usages {
573     my ( $self, $row, $metric_type ) = @_;
574
575     my @usage_months = $self->_get_usage_months( $self->{sushi}->{header} );
576
577     my @usage_months_fields = ();
578     my $count_total         = 0;
579
580     foreach my $usage_month (@usage_months) {
581         my $month_is_empty = 1;
582
583         foreach my $performance ( @{ $row->{Performance} } ) {
584             my $period             = $performance->{Period};
585             my $period_usage_month = substr( $period->{Begin_Date}, 0, 7 );
586
587             my $instances = $performance->{Instance};
588             my @metric_type_count =
589               map( $_->{Metric_Type} eq $metric_type ? $_->{Count} : (),
590                 @{$instances} );
591
592             if ( $period_usage_month eq $usage_month && $metric_type_count[0] )
593             {
594                 push( @usage_months_fields, $metric_type_count[0] );
595                 $count_total += $metric_type_count[0];
596                 $month_is_empty = 0;
597             }
598         }
599
600         if ($month_is_empty) {
601             push( @usage_months_fields, 0 );
602         }
603     }
604     return ( $count_total, @usage_months_fields );
605 }
606
607 =head3 _COUNTER_report_body
608
609 Return the COUNTER report body as an array
610
611 =cut
612
613 sub _COUNTER_report_body {
614     my ($self) = @_;
615
616     my $header = $self->{sushi}->{header};
617     my $body   = $self->{sushi}->{body};
618
619     my @metric_types_string = $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "Metric_Type" );
620     my @metric_types        = split( /\|/, $metric_types_string[0] );
621
622     my @report_body = ();
623
624     my $total_records = 0;
625     foreach my $report_row ( @{$body} ) {
626
627         my @metric_types = ();
628
629         # Grab all metric_types this SUSHI result has statistics for
630         foreach my $performance ( @{ $report_row->{Performance} } ) {
631             my @SUSHI_metric_types =
632               map( $_->{Metric_Type}, @{ $performance->{Instance} } );
633
634             foreach my $sushi_metric_type (@SUSHI_metric_types) {
635                 push( @metric_types, $sushi_metric_type )
636                   unless grep { $_ eq $sushi_metric_type } @metric_types;
637             }
638         }
639
640         # Add one report row for each metric_type we're working with
641         foreach my $metric_type (@metric_types) {
642             push( @report_body,
643                 $self->_COUNTER_report_row( $report_row, $metric_type ) );
644         }
645         $self->{total_records} = ++$total_records;
646     }
647
648     return @report_body;
649 }
650
651 =head3 _get_SUSHI_Name_Value
652
653 Returns "Value" of a given "Name"
654
655 =cut
656
657 sub _get_SUSHI_Name_Value {
658     my ( $self, $item, $name ) = @_;
659
660     my @value = map( $_->{Name} eq $name ? $_->{Value} : (), @{$item} );
661
662     return $value[0];
663 }
664
665 =head3 _get_SUSHI_Type_Value
666
667 Returns "Value" of a given "Type"
668
669 =cut
670
671 sub _get_SUSHI_Type_Value {
672     my ( $self, $item, $type ) = @_;
673
674     my @value = map( $_->{Type} eq $type ? $_->{Value} : (), @{$item} );
675
676     return $value[0];
677 }
678
679 =head3 _COUNTER_report_column_headings
680
681 Returns column headings by report type
682   Check the report type from the COUNTER header
683   and return column headings accordingly
684
685 =cut
686
687 sub _COUNTER_report_column_headings {
688     my ($self) = @_;
689
690     my $header = $self->{sushi}->{header};
691
692     if ( $header->{Report_ID} =~ /PR/i ) {
693         return $self->_COUNTER_platforms_report_column_headings;
694     }
695     elsif ( $header->{Report_ID} =~ /DR/i ) {
696         return $self->_COUNTER_databases_report_column_headings;
697     }
698     elsif ( $header->{Report_ID} =~ /IR/i ) {
699         return $self->_COUNTER_items_report_column_headings;
700     }
701     elsif ( $header->{Report_ID} =~ /TR/i ) {
702         return $self->_COUNTER_titles_report_column_headings;
703     }
704
705     return;
706 }
707
708 =head3 _COUNTER_items_report_column_headings
709
710 Return items report column headings
711
712 =cut
713
714 sub _COUNTER_items_report_column_headings {
715     my ($self) = @_;
716
717     my $header         = $self->{sushi}->{header};
718     my @month_headings = $self->_get_usage_months( $header, 1 );
719
720     return (
721         [
722             "Item",
723             "Publisher",
724             "Publisher_ID",
725             "Platform",
726
727             # "Authors", #IR_A1 only
728             # "Publication_Date", #IR_A1 only
729             # "Article_Version", #IR_A1 only
730             "DOI",
731             "Proprietary_ID",
732
733             # "ISBN", #IR only
734             # "Print_ISSN", #IR_A1 only
735             # "Online_ISSN", #IR_A1 only
736             "URI",
737
738             # "Parent_Title", #IR_A1 only
739             # "Parent_Authors", #IR_A1 only
740             # "Parent_Publication_Date", #IR only
741             # "Parent_Article_Version", #IR_A1 only
742             # "Parent_Data_Type", #IR only
743             # "Parent_DOI", #IR_A1 only
744             # "Parent_Proprietary_ID", #IR_A1 only
745             # "Parent_ISBN", #IR only
746             # "Parent_Print_ISSN", #IR_A1 only
747             # "Parent_Online_ISSN", #IR_A1 only
748             # "Parent_URI", #IR_A1 only
749             # "Component_Title", #IR only
750             # "Component_Authors", #IR only
751             # "Component_Publication_Date", #IR only
752             # "Component_Data_Type", #IR only
753             # "Component_DOI", #IR only
754             # "Component_Proprietary_ID", #IR only
755             # "Component_ISBN", #IR only
756             # "Component_Print_ISSN", #IR only
757             # "Component_Online_ISSN", #IR only
758             # "Component_URI", #IR only
759             # "Data_Type", #IR only
760             # "YOP", #IR only
761             # "Access_Type", #IR_A1 only
762             # "Access_Method", #IR only
763             "Metric_Type",
764             "Reporting_Period_Total",
765
766 # @month_headings in "Mmm-yyyy" format. TODO: Show unless Exclude_Monthly_Details=true
767             @month_headings
768         ]
769     );
770 }
771
772 =head3 _COUNTER_databases_report_column_headings
773
774 Return databases report column headings
775
776 =cut
777
778 sub _COUNTER_databases_report_column_headings {
779     my ($self) = @_;
780
781     my $header         = $self->{sushi}->{header};
782     my @month_headings = $self->_get_usage_months( $header, 1 );
783
784     return (
785         [
786             "Database",
787             "Publisher",
788             "Publisher_ID",
789             "Platform",
790             "Proprietary_ID",
791             "Metric_Type",
792             "Reporting_Period_Total",
793
794 # @month_headings in "Mmm-yyyy" format. TODO: Show unless Exclude_Monthly_Details=true
795             @month_headings
796         ]
797     );
798 }
799
800 =head3 _COUNTER_platforms_report_column_headings
801
802 Return platforms report column headings
803
804 =cut
805
806 sub _COUNTER_platforms_report_column_headings {
807     my ($self) = @_;
808
809     my $header         = $self->{sushi}->{header};
810     my @month_headings = $self->_get_usage_months( $header, 1 );
811
812     return (
813         [
814             "Platform",
815             "Metric_Type",
816             "Reporting_Period_Total",
817
818 # @month_headings in "Mmm-yyyy" format. TODO: Show unless Exclude_Monthly_Details=true
819             @month_headings
820         ]
821     );
822 }
823
824 =head3 _COUNTER_titles_report_column_headings
825
826 Return titles report column headings
827
828 =cut
829
830 sub _COUNTER_titles_report_column_headings {
831     my ($self) = @_;
832
833     my $header         = $self->{sushi}->{header};
834     my @month_headings = $self->_get_usage_months( $header, 1 );
835     my $specific_fields =
836       $self->get_report_type_specific_fields( $header->{Report_ID} );
837
838     return (
839         [
840             "Title",
841             "Publisher",
842             "Publisher_ID",
843             "Platform",
844             "DOI",
845             "Proprietary_ID",
846             grep ( /ISBN/, @{$specific_fields} ) ? ("ISBN") : (),
847             "Print_ISSN",
848             "Online_ISSN",
849             "URI",
850
851             #"Data_Type", #TODO: Only if requested (?)
852             #"Section_Type", #TODO: Only if requested (?)
853             grep ( /YOP/,         @{$specific_fields} ) ? ("YOP")         : (),
854             grep ( /Access_Type/, @{$specific_fields} ) ? ("Access_Type") : (),
855
856             #"Access_Method", #TODO: Only if requested (?)
857             "Metric_Type",
858             "Reporting_Period_Total",
859
860 # @month_headings in "Mmm-yyyy" format. TODO: Show unless Exclude_Monthly_Details=true
861             @month_headings
862         ]
863     );
864 }
865
866 =head3 _get_usage_months
867
868 Return report usage months. Formatted for column headings if $column_headings_formatting
869
870 =cut
871
872 sub _get_usage_months {
873     my ( $self, $header, $column_headings_formatting ) = @_;
874
875     my @months = (
876         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
877         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
878     );
879
880     my @begin_date = map( $_->{Name} eq "Begin_Date" ? $_->{Value} : (),
881         @{ $header->{Report_Filters} } );
882     my $begin_month = substr( $begin_date[0], 5, 2 );
883     my $begin_year  = substr( $begin_date[0], 0, 4 );
884
885     my @end_date = map( $_->{Name} eq "End_Date" ? $_->{Value} : (),
886         @{ $header->{Report_Filters} } );
887     my $end_month = substr( $end_date[0], 5, 2 );
888     my $end_year  = substr( $end_date[0], 0, 4 );
889
890     my @month_headings = ();
891     while ( $begin_month <= $end_month || $begin_year < $end_year ) {
892         push( @month_headings,
893               $column_headings_formatting
894             ? $months[ $begin_month - 1 ] . " " . $begin_year
895             : $begin_year . "-" . $begin_month );
896         $begin_month++;
897         if ( $begin_month > 12 ) {
898             $begin_month = 1;
899             $begin_year++;
900         }
901         $begin_month = "0" . $begin_month if length($begin_month) == 1;
902     }
903
904     return @month_headings;
905 }
906
907 =head3 get_report_type_specific_fields
908
909 Returns the specific fields for a given report_type
910
911 =cut
912
913 sub get_report_type_specific_fields {
914     my ( $self, $report_type ) = @_;
915
916     my %report_type_map = (
917         "TR_B1" => [ 'YOP', 'ISBN' ],
918         "TR_B2" => [ 'YOP', 'ISBN' ],
919         "TR_B3" => [ 'YOP', 'Access_Type', 'ISBN' ],
920         "TR_J3" => ['Access_Type'],
921         "TR_J4" => ['YOP'],
922     );
923
924     return $report_type_map{$report_type};
925
926 }
927
928 =head3 test_connection
929
930 Tests the connection of the harvester to the SUSHI service and returns any alerts of planned SUSHI outages
931
932 =cut
933
934 sub test_connection {
935     my ($self) = @_;
936
937     my $url = $self->service_url;
938     $url .= '/status';
939     $url .= '?customer_id=' . $self->customer_id;
940     $url .= '&requestor_id=' . $self->requestor_id if $self->requestor_id;
941     $url .= '&api_key=' . $self->api_key           if $self->api_key;
942
943     my $request  = HTTP::Request->new( 'GET' => $url );
944     my $ua       = LWP::UserAgent->new;
945     my $response = $ua->simple_request($request);
946
947     my @result = decode_json( $response->decoded_content );
948     if ( $result[0][0]->{Service_Active} ) {
949         return 1;
950     }
951     else {
952         return 0;
953     }
954
955 }
956
957 =head3 erm_usage_titles
958
959 Method to embed erm_usage_titles to titles for report formatting
960
961 =cut
962
963 sub erm_usage_titles {
964     my ($self) = @_;
965     my $usage_title_rs = $self->_result->erm_usage_titles;
966     return Koha::ERM::UsageTitles->_new_from_dbic($usage_title_rs);
967 }
968
969 =head3 erm_usage_muses
970
971 Method to embed erm_usage_muses to titles for report formatting
972
973 =cut
974
975 sub erm_usage_muses {
976     my ($self) = @_;
977     my $usage_mus_rs = $self->_result->erm_usage_muses;
978     return Koha::ERM::MonthlyUsages->_new_from_dbic($usage_mus_rs);
979 }
980
981
982
983 =head3 erm_usage_platforms
984
985 Method to embed erm_usage_platforms to platforms for report formatting
986
987 =cut
988
989 sub erm_usage_platforms {
990     my ( $self ) = @_;
991     my $usage_platform_rs = $self->_result->erm_usage_platforms;
992     return Koha::ERM::UsagePlatforms->_new_from_dbic($usage_platform_rs);
993 }
994
995 =head3 erm_usage_items
996
997 Method to embed erm_usage_items to items for report formatting
998
999 =cut
1000
1001 sub erm_usage_items {
1002     my ( $self ) = @_;
1003     my $usage_item_rs = $self->_result->erm_usage_items;
1004     return Koha::ERM::UsageItems->_new_from_dbic($usage_item_rs);
1005 }
1006
1007 =head3 erm_usage_databases
1008
1009 Method to embed erm_usage_databases to databases for report formatting
1010
1011 =cut
1012
1013 sub erm_usage_databases {
1014     my ( $self ) = @_;
1015     my $usage_database_rs = $self->_result->erm_usage_databases;
1016     return Koha::ERM::UsageDatabases->_new_from_dbic($usage_database_rs);
1017 }
1018
1019 =head3 _type
1020
1021 =cut
1022
1023 sub _type {
1024     return 'ErmUsageDataProvider';
1025 }
1026
1027 1;