Bug 40383: Add JSON representation to Koha::Edifact and JSON download to edimsg.pl
This patch implements a JSON representation of EDIFACT interchanges for enhanced
programmatic access and debugging capabilities.
Backend Changes:
- Add to_json() method to Koha::Edifact module
- Structured JSON output with header, messages array, and trailer
- Segment parsing with raw data, elements arrays, and line_id tracking
- Line item grouping using LIN segment identifiers
Frontend Changes:
- Add JSON download button to edimsg.pl page
- Support for ?format=json parameter and Accept: application/json header
- Proper HTTP content-type handling for JSON responses
- Error handling for missing messages in JSON format
JSON Structure:
{
"header": "UNB+UNOC:3+...",
"messages": [
{
"header": "UNH+1+QUOTES:D:93A:UN:EAN008'",
"segments": [
{
"tag": "LIN",
"raw": "LIN+1+1+9780552150040:EN'",
"elements": ["1", "1", "9780552150040:EN"],
"line_id": "1"
}
],
"trailer": "UNT+25+1'"
}
],
"trailer": "UNZ+1+1'"
}
Test plan:
1. Apply this patch
2. Ensure you have EDIFACT messages in your system
3. Go to Acquisitions > EDIFACT messages
4. Click "View message" for any message
5. Click the new "JSON" button
6. Verify JSON downloads with proper structure
7. Verify direct URL access with ?format=json parameter works
8. Test with Accept: application/json header
Signed-off-by: Hannah Dunne-Howrie <hdunne-howrie@westminster.gov.uk>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
This commit is contained in:
parent
e8cdc4e9d5
commit
1f5f91e649
115
Koha/Edifact.pm
115
Koha/Edifact.pm
|
|
@ -21,6 +21,7 @@ use strict;
|
|||
use warnings;
|
||||
use File::Slurp qw( read_file );
|
||||
use Carp qw( carp croak );
|
||||
use JSON qw( encode_json );
|
||||
use Koha::Edifact::Segment;
|
||||
use Koha::Edifact::Message;
|
||||
|
||||
|
|
@ -172,6 +173,113 @@ sub message_array {
|
|||
return $msg_arr;
|
||||
}
|
||||
|
||||
sub to_json {
|
||||
my $self = shift;
|
||||
|
||||
# Find interchange header (UNB)
|
||||
my $header_seg;
|
||||
my $trailer_seg;
|
||||
my @all_segments = @{ $self->{transmission} };
|
||||
|
||||
# Get header and trailer
|
||||
for my $i ( 0 .. $#all_segments ) {
|
||||
if ( $all_segments[$i]->tag eq 'UNB' ) {
|
||||
$header_seg = $all_segments[$i];
|
||||
} elsif ( $all_segments[$i]->tag eq 'UNZ' ) {
|
||||
$trailer_seg = $all_segments[$i];
|
||||
}
|
||||
}
|
||||
|
||||
# Build messages array
|
||||
my @json_messages;
|
||||
my $current_message;
|
||||
my @current_segments;
|
||||
my $current_line_id;
|
||||
|
||||
for my $segment (@all_segments) {
|
||||
my $tag = $segment->tag;
|
||||
|
||||
if ( $tag eq 'UNH' ) {
|
||||
|
||||
# Start new message
|
||||
if ($current_message) {
|
||||
$current_message->{segments} = [@current_segments];
|
||||
push @json_messages, $current_message;
|
||||
}
|
||||
$current_message = {
|
||||
header => $segment->as_string,
|
||||
segments => [],
|
||||
};
|
||||
@current_segments = ();
|
||||
$current_line_id = undef;
|
||||
} elsif ( $tag eq 'UNT' ) {
|
||||
|
||||
# End current message
|
||||
if ($current_message) {
|
||||
$current_message->{trailer} = $segment->as_string;
|
||||
$current_message->{segments} = [@current_segments];
|
||||
push @json_messages, $current_message;
|
||||
}
|
||||
$current_message = undef;
|
||||
@current_segments = ();
|
||||
$current_line_id = undef;
|
||||
} elsif ( $tag eq 'UNB' || $tag eq 'UNZ' ) {
|
||||
|
||||
# Skip - these are handled separately
|
||||
next;
|
||||
} elsif ($current_message) {
|
||||
|
||||
# Process segment within message
|
||||
my $segment_data = {
|
||||
tag => $tag,
|
||||
raw => $segment->as_string,
|
||||
elements => [],
|
||||
};
|
||||
|
||||
# Extract elements
|
||||
my $elem_count = 0;
|
||||
while ( defined( my $elem = $segment->elem($elem_count) ) ) {
|
||||
push @{ $segment_data->{elements} }, $elem;
|
||||
$elem_count++;
|
||||
}
|
||||
|
||||
# Handle line_id assignment
|
||||
if ( $tag eq 'LIN' ) {
|
||||
$current_line_id = $segment->elem(0);
|
||||
$segment_data->{line_id} = $current_line_id if defined $current_line_id;
|
||||
} elsif ( $tag eq 'UNS' ) {
|
||||
|
||||
# UNS (Section Control) marks transition to message summary
|
||||
# Reset line_id so subsequent segments are treated as message-level
|
||||
$current_line_id = undef;
|
||||
} elsif ( defined $current_line_id && $tag !~ /^(UNH|UNT|UNS|CNT)$/ ) {
|
||||
|
||||
# Assign line_id to segments that belong to current line item
|
||||
# Exclude message control segments (UNH, UNT, UNS, CNT)
|
||||
# After UNS, current_line_id is undefined, so segments are message-level
|
||||
$segment_data->{line_id} = $current_line_id;
|
||||
}
|
||||
|
||||
push @current_segments, $segment_data;
|
||||
}
|
||||
}
|
||||
|
||||
# Handle any remaining message
|
||||
if ($current_message) {
|
||||
$current_message->{segments} = [@current_segments];
|
||||
push @json_messages, $current_message;
|
||||
}
|
||||
|
||||
# Build final structure
|
||||
my $json_structure = {
|
||||
header => $header_seg ? $header_seg->as_string : undef,
|
||||
messages => \@json_messages,
|
||||
trailer => $trailer_seg ? $trailer_seg->as_string : undef,
|
||||
};
|
||||
|
||||
return encode_json($json_structure);
|
||||
}
|
||||
|
||||
#
|
||||
# internal parsing routines used in _init
|
||||
#
|
||||
|
|
@ -291,6 +399,13 @@ Edifact - Edifact message handler
|
|||
|
||||
return an array of Message objects contained in the Edifact transmission
|
||||
|
||||
=head2 to_json
|
||||
|
||||
returns a JSON representation of the complete edifact interchange.
|
||||
The JSON structure includes the interchange header, all messages with their
|
||||
segments (both raw and parsed), and the interchange trailer.
|
||||
Line items are grouped using line_id extracted from LIN segments.
|
||||
|
||||
=head1 Internal Methods
|
||||
|
||||
=head2 _init
|
||||
|
|
|
|||
|
|
@ -20,11 +20,18 @@ use Modern::Perl;
|
|||
|
||||
use CGI;
|
||||
use Koha::Database;
|
||||
use Koha::Edifact;
|
||||
use C4::Koha;
|
||||
use C4::Auth qw( get_template_and_user );
|
||||
use C4::Output qw( output_html_with_http_headers );
|
||||
use C4::Output qw( output_html_with_http_headers output_with_http_headers );
|
||||
|
||||
my $q = CGI->new;
|
||||
|
||||
# Check if JSON output is requested
|
||||
my $format = $q->param('format') || '';
|
||||
my $accept_header = $ENV{HTTP_ACCEPT} || '';
|
||||
my $wants_json = ( $format eq 'json' ) || ( $accept_header =~ m|application/json| );
|
||||
|
||||
my ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
|
||||
{
|
||||
template_name => 'acqui/edimsg.tt',
|
||||
|
|
@ -33,17 +40,39 @@ my ( $template, $loggedinuser, $cookie, $userflags ) = get_template_and_user(
|
|||
flagsrequired => { acquisition => 'edi_manage' },
|
||||
}
|
||||
);
|
||||
|
||||
my $msg_id = $q->param('id');
|
||||
my $schema = Koha::Database->new()->schema();
|
||||
|
||||
my $msg = $schema->resultset('EdifactMessage')->find($msg_id);
|
||||
|
||||
if ($msg) {
|
||||
my $transmission = $msg->raw_msg;
|
||||
|
||||
my @segments = segmentize($transmission);
|
||||
$template->param( segments => \@segments );
|
||||
if ($wants_json) {
|
||||
|
||||
# Return JSON representation using Koha::Edifact
|
||||
my $edifact = Koha::Edifact->new( { transmission => $transmission } );
|
||||
my $json_output = $edifact->to_json();
|
||||
|
||||
output_with_http_headers( $q, $cookie, $json_output, 'json', '200 OK' );
|
||||
exit;
|
||||
} else {
|
||||
|
||||
# Return HTML representation (existing behavior)
|
||||
my @segments = segmentize($transmission);
|
||||
$template->param(
|
||||
segments => \@segments,
|
||||
id => $msg_id
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$template->param( no_message => 1 );
|
||||
if ($wants_json) {
|
||||
output_with_http_headers( $q, $cookie, '{"error":"Message not found"}', 'json', '404 Not Found' );
|
||||
exit;
|
||||
} else {
|
||||
$template->param( no_message => 1 );
|
||||
}
|
||||
}
|
||||
|
||||
output_html_with_http_headers( $q, $cookie, $template->output );
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@
|
|||
[% IF no_message %]
|
||||
<div class="alert alert-info">The requested message cannot be displayed</div>
|
||||
[% ELSE %]
|
||||
<div class="row">
|
||||
<div class="col-sm-2">
|
||||
<div id="toolbar" class="btn-toolbar">
|
||||
<a class="btn btn-default btn-sm" href="[% "/cgi-bin/koha/acqui/edimsg.pl?id=" _ id _ "&format=json" %]" target="_blank"> <i class="fa fa-download"></i> JSON </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
[% FOREACH seg IN segments %]
|
||||
<li>[% seg | html %]</li>
|
||||
|
|
|
|||
Loading…
Reference in New Issue