From: Bill Erickson Date: Wed, 25 May 2016 21:40:17 +0000 (-0400) Subject: LP#1373690 Attribute-based EDI generator X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=20d7df2b54f3d7e9539da73a625b0dbaae705e0c;p=evergreen%2Fjoelewis.git LP#1373690 Attribute-based EDI generator New Perl module Utils::EDIWriter for buliding EDI ORDERS messages. Vendor-specific toggles live in new database tables (acq.edi_attr, acq.edi_attr_set, acq.edi_attr_set_map). The combination of these 2 replaces the current JEDI Action/Trigger template with toggle embedded in the template. Signed-off-by: Bill Erickson Signed-off-by: Mike Rylander --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index d075d354c3..7f6e192ce0 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -9213,10 +9213,12 @@ SELECT usr, + + @@ -9236,6 +9238,65 @@ SELECT usr, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIWriter.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIWriter.pm new file mode 100644 index 0000000000..6d825f1e82 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Utils/EDIWriter.pm @@ -0,0 +1,587 @@ +# --------------------------------------------------------------- +# Copyright (C) 2016 King County Library System +# Author: Bill Erickson +# +# Copied heavily from Application/Trigger/Reactor.pm +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# --------------------------------------------------------------- +package OpenILS::Utils::EDIWriter; +use strict; use warnings; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Application::AppUtils; +use DateTime; +my $U = 'OpenILS::Application::AppUtils'; + +sub new { + my ($class, $args) = @_; + $args ||= {}; + return bless($args, $class); +} + +# Returns EDI string on success, undef on error. +sub write { + my ($self, $po_id, $msg_type) = @_; + $msg_type ||= 'order'; + + my $po = $self->get_po($po_id); + return undef unless $po; + + $self->compile_po($po); + return undef unless $self->{compiled}; + + my $edi = $self->build_order_edi if $msg_type eq 'order'; + + # remove the newlines unless we are pretty printing + $edi =~ s/\n//g unless $self->{pretty}; + + return $edi; +} + +sub get_po { + my ($self, $po_id) = @_; + return new_editor()->retrieve_acq_purchase_order([ + $po_id, { + flesh => 5, + flesh_fields => { + acqpo => [qw/lineitems ordering_agency provider/], + acqpro => [qw/edi_default/], + acqedi => [qw/attr_set/], + aeas => [qw/attr_maps/], + jub => [qw/lineitem_details lineitem_notes attributes/], + acqlid => [qw/owning_lib location fund eg_copy_id/], + acp => [qw/location call_number/], + aou => [qw/mailing_address/] + } + } + ]); +} + +sub escape_edi { + my ($self, $value) = @_; + return '' if (not defined $value || ref($value)); + + # Typical vendors dealing with EDIFACT (or is the problem with + # our EDI translator itself?) would seem not to want + # any characters outside the ASCII range, so trash them. + $value =~ s/[^[:ascii:]]//g; + + # What the heck, get rid of [ ] too (although I couldn't get them + # to cause any problems for me, problems have been reported. See + # LP #812593). + $value =~ s/[\[\]]//g; + + # Characters [? + ' \ : ] are all potentially problematic for + # EDI messages, regardless of their position in the string. + # Safest to simply remove them. + $value =~ s/[\\\?\+':]//g; + + # Replace newlines with spaces. + $value =~ s/\n/ /g; + + return $value; +} + +# Returns an EDI-escaped version of the requested lineitem attribute +# value. If $attr_type is not set, the first attribute found matching +# the requested $attr_name will be used. +sub get_li_attr { + my ($self, $li, $attr_name, $attr_type) = @_; + + for my $attr (@{$li->attributes}) { + next unless $attr->attr_name eq $attr_name; + next if $attr_type && $attr->attr_type ne $attr_type; + return $self->escape_edi($attr->attr_value); + } + + return ''; +} + +# Generates a HASH version of the PO with all of the data necessary +# to generate an EDI message from the PO. +sub compile_po { + my ($self, $po) = @_; + + # Cannot generate EDI if the PO has no linked EDI account. + return undef unless $po->provider->edi_default; + + my %compiled = ( + po_id => $po->id, + po_name => $self->escape_edi($po->name), + provider_id => $po->provider->id, + vendor_san => $po->provider->san || '', + org_unit_san => $po->ordering_agency->mailing_address->san || '', + currency_type => $po->provider->currency_type, + edi_attrs => {}, + lineitems => [] + ); + + $self->{compiled} = \%compiled; + + if ($po->provider->edi_default->attr_set) { + $compiled{edi_attrs}{$_->attr} = 1 + for @{$po->provider->edi_default->attr_set->attr_maps} + } + + $compiled{buyer_code} = + $compiled{edi_attrs}->{BUYER_ID_INCLUDE_VENDCODE} ? # B&T + $compiled{vendor_san}.' '.$po->provider->edi_default->vendcode : + $po->provider->edi_default->vendacct; + + push(@{$compiled{lineitems}}, + $self->compile_li($_)) for @{$po->lineitems}; + + return \%compiled; +} + +# Translate a lineitem order identifier attribute into an +# EDI ID value and ID qualifier. +sub set_li_order_ident { + my ($self, $li, $li_hash) = @_; + + my $idqual = 'EN'; # ISBN13 + my $idval = ''; + + if ($self->{compiled}->{edi_attr}->{LINEITEM_IDENT_VENDOR_NUMBER}) { + # See if we have a vendor-specific lineitem identifier value + $idval = $self->get_li_attr($li, 'vendor_num'); + } + + if (!$idval) { + + my $attr = $self->get_li_order_ident_attr($li->attributes); + + if ($attr) { + my $name = $attr->attr_name; + $idval = $attr->attr_value; + + if ($name eq 'isbn' && length($idval) != 13) { + $idqual = 'IB'; + } elsif ($name eq 'issn') { + $idqual = 'IS'; + } + } else { + $idqual = 'IN'; + $idval = $li->id; + } + } + + $li_hash->{idqual} = $idqual; + $li_hash->{idval} = $idval; +} + +# Find the acq.lineitem_attr object that represents the identifier +# for a lineitem. +sub get_li_order_ident_attr { + my ($self, $attrs) = @_; + + # preferred identifier + my ($attr) = grep { $U->is_true($_->order_ident) } @$attrs; + return $attr if $attr; + + # note we're not using get_li_attr, since we need the + # attr object and not just the attr value + + # isbn-13 + ($attr) = grep { + $_->attr_name eq 'isbn' and + $_->attr_type eq 'lineitem_marc_attr_definition' and + length($_->attr_value) == 13 + } @$attrs; + return $attr if $attr; + + for my $name (qw/isbn issn upc/) { + ($attr) = grep { + $_->attr_name eq $name and + $_->attr_type eq 'lineitem_marc_attr_definition' + } @$attrs; + return $attr if $attr; + } + + # any 'identifier' attr + return (grep { $_->attr_name eq 'identifier' } @$attrs)[0]; +} + +# Collect FTX notes and chop them into FTX-compatible values. +sub get_li_ftx { + my ($self, $li) = @_; + + # all vendor-public, non-empty lineitem notes + my @notes = + map {$_->value} + grep { $U->is_true($_->vendor_public) && $_->value } + @{$li->lineitem_notes}; + + if ($self->{compiled}->{edi_attr}->{COPY_SPEC_CODES}) { + for my $lid (@{$li->lineitem_details}) { + push(@notes, $lid->note) + if ($lid->note || '') =~ /spec code [a-zA-Z0-9_]/; + } + } + + my @trimmed_notes; + + if (!@notes && $self->{compiled}->{edi_attr}->{INCLUDE_EMPTY_LI_NOTE}) { + # lineitem has no notes. Add a blank note if needed. + push(@trimmed_notes, ''); + + } else { + # EDI FTX fields have a max length of 512 + # While we're in here, EDI-escape the note values + for my $note (@notes) { + $note = $self->escape_edi($note); + my @parts = ($note =~ m/.{1,512}/g); + push(@trimmed_notes, @parts); + } + } + + return \@trimmed_notes; +} + +sub compile_li { + my ($self, $li) = @_; + + my $li_hash = { + id => $li->id, + quantity => scalar(@{$li->lineitem_details}), + estimated_unit_price => $li->estimated_unit_price || '0.00', + notes => $self->get_li_ftx($li), + copies => [] + }; + + $self->set_li_order_ident($li, $li_hash); + + for my $name (qw/title author edition pubdate publisher pagination/) { + $li_hash->{$name} = $self->get_li_attr($li, $name); + } + + $self->compile_copies($li, $li_hash); + + return $li_hash; +} + +sub compile_copies { + my ($self, $li, $li_hash) = @_; + + # does this EDI account want copy data? + return unless $self->{compiled}->{edi_attrs}->{INCLUDE_COPIES}; + + for my $copy (@{$li->lineitem_details}) { + $self->compile_copy($li, $li_hash, $copy); + } +} + +sub compile_copy { + my ($self, $li, $li_hash, $copy) = @_; + + my $fund = $copy->fund ? $copy->fund->code : ''; + my $item_type = $copy->circ_modifier || ''; + my $call_number = $copy->cn_label || ''; + my $owning_lib = $copy->owning_lib ? $copy->owning_lib->shortname : ''; + my $location = $copy->location ? $copy->location->name : ''; + my $collection_code = $copy->collection_code || ''; + my $barcode = $copy->barcode || ''; + + + # When an ACQ copy links to a real copy (acp), treat the real + # copy as authoritative for certain fields. + my $acp = $copy->eg_copy_id; + if ($acp) { + $item_type = $acp->circ_modifier || ''; + $call_number = $acp->call_number->label; + $location = $acp->location->name; + } + + my $found_match = 0; + + # Collapse like copies into groups with a quantity value. + # INCLUDE_COPY_ID implies one GIR row per copy, no collapsing. + if (!$self->{compiled}->{edi_attrs}->{INCLUDE_COPY_ID}) { + + for my $e_copy (@{$li_hash->{copies}}) { + if ( + ($fund eq $e_copy->{fund}) && + ($item_type eq $e_copy->{item_type}) && + ($call_number eq $e_copy->{call_number}) && + ($owning_lib eq $e_copy->{owning_lib}) && + ($location eq $e_copy->{location}) && + ($barcode eq $e_copy->{barcode}) && + ($collection_code eq $e_copy->{collection_code}) + ) { + $e_copy->{quantity}++; + $found_match = 1; + last; + } + } + } + + return if $found_match; # nothing left to do. + + # No matching copy found. Add it as a new copy to the lineitem + # copies array. + + push(@{$li_hash->{copies}}, { + fund => $self->escape_edi($fund), + item_type => $self->escape_edi($item_type), + call_number => $self->escape_edi($call_number), + owning_lib => $self->escape_edi($owning_lib), + location => $self->escape_edi($location), + barcode => $self->escape_edi($barcode), + collection_code => $self->escape_edi($collection_code), + copy_id => $copy->id, # for INCLUDE_COPY_ID + quantity => 1 + }); +} + +# IMD fields are limited to 70 chars per value. Any values longer +# should be carried via repeating IMD fields. +# IMD fields should only display the +::: when a value is present +sub IMD { + my ($self, $code, $value) = @_; + if ($value) { + my $s = ''; + for my $part ($value =~ m/.{1,70}/g) { + $s .= "IMD+F+$code+:::$part'\n"; } + return $s; + + } else { + return "IMD+F+$code'\n" + } +} + +# EDI Segments: -- +# UNA +# UNB +# UNH +# BGM +# DTM +# NAD+BY +# NAD+SU...::31B +# NAD+SU...::92 +# CUX +# +# UNS +# CNT +# UNT +# UNZ +sub build_order_edi { + my ($self) = @_; + my %c = %{$self->{compiled}}; + my $date = DateTime->now->strftime("%Y%m%d"); + my $datetime = DateTime->now->strftime("%y%m%d:%H%M"); + my @lis = @{$c{lineitems}}; + + # EDI header + my $edi = <{compiled}->{edi_attrs}->{BUYER_ID_INCLUDE_VENDCODE}; + + $edi .= <build_lineitem_segments($_) for @lis; + + my $li_count = scalar(@lis); + + # Count the number of segments in the EDI message by counting the + # number of newlines. Add to count for lines below, not including + # the UNZ segment. + my $segments = $edi =~ tr/\n//; + $segments += 1; # UNS, CNT, UNT, but not UNA or UNB + + # EDI Trailer + $edi .= <{compiled}}; + + my $id = $li_hash->{id}; + my $idval = $li_hash->{idval}; + my $idqual = $li_hash->{idqual}; + my $quantity = $li_hash->{quantity}; + my $price = $li_hash->{estimated_unit_price}; + + # Line item identifier segments + my $edi = "LIN+$id++$idval:$idqual'\n"; + $edi .= "PIA+5+$idval:$idqual'\n"; + + $edi .= $self->IMD('BTI', $li_hash->{title}); + $edi .= $self->IMD('BPU', $li_hash->{publisher}); + $edi .= $self->IMD('BPD', $li_hash->{pubdate}); + + $edi .= $self->IMD('BEN', $li_hash->{edition}) + if $c{edi_attrs}->{INCLUDE_BIB_EDITION}; + + $edi .= $self->IMD('BAU', $li_hash->{author}) + if $c{edi_attrs}->{INCLUDE_BIB_AUTHOR}; + + $edi .= $self->IMD('BPH', $li_hash->{pagination}) + if $c{edi_attrs}->{INCLUDE_BIB_PAGINATION}; + + $edi .= "QTY+21:$quantity'\n"; + + $edi .= $self->build_gir_segments($li_hash); + + for my $note (@{$li_hash->{notes}}) { + if ($note) { + $edi .= "FTX+LIN+1+$note'\n" + } else { + $edi .= "FTX+LIN+1'\n" + } + } + + $edi .= "PRI+AAB:$price'\n"; + + # Standard RFF + my $rff = "$c{po_id}/$id"; + + if ($c{edi_attrs}->{LINEITEM_REF_ID_ONLY}) { + # RFF with lineitem ID only (typically B&T) + $rff = $id; + } elsif ($c{edi_attrs}->{INCLUDE_PO_NAME}) { + # RFF with PO name instead of PO ID + $rff = "$c{po_name}/$id"; + } + + $edi .= "RFF+LI:$rff'\n"; + + return $edi; +} + + +# Map of GIR segment codes, copy field names, inclusion attributes, +# and include-if-empty attributes for encoding copy data. +my @gir_fields = ( + { code => 'LLO', + field => 'owning_lib', + attr => 'INCLUDE_OWNING_LIB'}, + { code => 'LSQ', + field => 'collection_code', + attr => 'INCLUDE_COLLECTION_CODE', + empty_attr => 'INCLUDE_EMPTY_COLLECTION_CODE'}, + { code => 'LQT', + field => 'quantity', + attr => 'INCLUDE_QUANTITY'}, + { code => 'LCO', + field => 'copy_id', + attr => 'INCLUDE_COPY_ID'}, + { code => 'LST', + field => 'item_type', + attr => 'INCLUDE_ITEM_TYPE', + empty_attr => 'INCLUDE_EMPTY_ITEM_TYPE'}, + { code => 'LSM', + field => 'call_number', + attr => 'INCLUDE_CALL_NUMBER', + empty_attr => 'INCLUDE_EMPTY_CALL_NUMBER'}, + { code => 'LFN', + field => 'fund', + attr => 'INCLUDE_FUND'}, + { code => 'LFH', + field => 'location', + attr => 'INCLUDE_LOCATION', + empty_attr => 'INCLUDE_EMPTY_LOCATION'}, + { code => 'LAC', + field => 'barcode', + attr => 'INCLUDE_ITEM_BARCODE'} +); + +# EDI Segments: -- +# GIR +# Sub-Segments: -- +# LLO +# LFN +# LSM +# LST +# LSQ +# LFH +# LQT +sub build_gir_segments { + my ($self, $li_hash) = @_; + my %c = %{$self->{compiled}}; + my $gir_index = 0; + my $edi = ''; + + for my $copy (@{$li_hash->{copies}}) { + $gir_index++; + my $gir_idx_str = sprintf("%03d", $gir_index); + + my $field_count = 0; + for my $field (@gir_fields) { + next unless $c{edi_attrs}->{$field->{attr}}; + + my $val = $copy->{$field->{field}}; + my $code = $field->{code}; + + # include the GIR component if we have a value or this + # EDI account is configured to include the empty value + next unless $val || $c{edi_attrs}->{$field->{empty_attr} || ''}; + + # EDI only allows 5 fields per GIR segment. When we exceed + # 5, finalize the in-process GIR segment and add a new one + # as needed. + if ($field_count == 5) { + $field_count = 0; + # Finalize this GIR segment with a ' and newline + $edi .= "'\n"; + } + + $field_count++; + + # Starting a new GIR line for the current copy. + $edi .= "GIR+$gir_idx_str" if $field_count == 1; + + # Add the field-specific value + $edi .= "+$val:$code"; + } + + # End the final GIR segment with a ' and newline + $edi .= "'\n"; + } + + return $edi; +} + +1; + diff --git a/Open-ILS/src/sql/Pg/200.schema.acq.sql b/Open-ILS/src/sql/Pg/200.schema.acq.sql index 6391e68bc9..7fa60e32e8 100644 --- a/Open-ILS/src/sql/Pg/200.schema.acq.sql +++ b/Open-ILS/src/sql/Pg/200.schema.acq.sql @@ -739,11 +739,31 @@ CREATE TABLE acq.fiscal_year ( CONSTRAINT acq_fy_physical_key UNIQUE ( calendar, year_begin ) ); +CREATE TABLE acq.edi_attr ( + key TEXT PRIMARY KEY, + label TEXT NOT NULL UNIQUE +); + +CREATE TABLE acq.edi_attr_set ( + id SERIAL PRIMARY KEY, + label TEXT NOT NULL UNIQUE +); + +CREATE TABLE acq.edi_attr_set_map ( + id SERIAL PRIMARY KEY, + attr_set INTEGER NOT NULL REFERENCES acq.edi_attr_set(id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + attr TEXT NOT NULL REFERENCES acq.edi_attr(key) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + CONSTRAINT edi_attr_set_map_attr_once UNIQUE (attr_set, attr) +); + CREATE TABLE acq.edi_account ( -- similar tables can extend remote_account for other parts of EG provider INT NOT NULL REFERENCES acq.provider (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, in_dir TEXT, -- incoming messages dir (probably different than config.remote_account.path, the outgoing dir) vendcode TEXT, - vendacct TEXT + vendacct TEXT, + attr_set INTEGER REFERENCES acq.edi_attr_set(id) -- NULL OK ) INHERITS (config.remote_account); -- We need a UNIQUE constraint here also, to support the FK from acq.provider.edi_default diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 391ad5ae6a..8523778337 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -17187,3 +17187,150 @@ VALUES ( ); +INSERT INTO acq.edi_attr (key, label) VALUES + ('INCLUDE_PO_NAME', + oils_i18n_gettext('INCLUDE_PO_NAME', + 'Oders Include PO Name', 'aea', 'label')), + ('INCLUDE_COPIES', + oils_i18n_gettext('INCLUDE_COPIES', + 'Orders Include Copy Data', 'aea', 'label')), + ('INCLUDE_FUND', + oils_i18n_gettext('INCLUDE_FUND', + 'Orders Include Copy Funds', 'aea', 'label')), + ('INCLUDE_CALL_NUMBER', + oils_i18n_gettext('INCLUDE_CALL_NUMBER', + 'Orders Include Copy Call Numbers', 'aea', 'label')), + ('INCLUDE_ITEM_TYPE', + oils_i18n_gettext('INCLUDE_ITEM_TYPE', + 'Orders Include Copy Item Types', 'aea', 'label')), + ('INCLUDE_ITEM_BARCODE', + oils_i18n_gettext('INCLUDE_ITEM_BARCODE', + 'Orders Include Copy Barcodes', 'aea', 'label')), + ('INCLUDE_LOCATION', + oils_i18n_gettext('INCLUDE_LOCATION', + 'Orders Include Copy Locations', 'aea', 'label')), + ('INCLUDE_COLLECTION_CODE', + oils_i18n_gettext('INCLUDE_COLLECTION_CODE', + 'Orders Include Copy Collection Codes', 'aea', 'label')), + ('INCLUDE_OWNING_LIB', + oils_i18n_gettext('INCLUDE_OWNING_LIB', + 'Orders Include Copy Owning Library', 'aea', 'label')), + ('INCLUDE_QUANTITY', + oils_i18n_gettext('INCLUDE_QUANTITY', + 'Orders Include Copy Quantities', 'aea', 'label')), + ('INCLUDE_COPY_ID', + oils_i18n_gettext('INCLUDE_COPY_ID', + 'Orders Include Copy IDs', 'aea', 'label')), + ('BUYER_ID_INCLUDE_VENDCODE', + oils_i18n_gettext('BUYER_ID_INCLUDE_VENDCODE', + 'Buyer ID Qualifier Includes Vendcode', 'aea', 'label')), + ('INCLUDE_BIB_EDITION', + oils_i18n_gettext('INCLUDE_BIB_EDITION', + 'Order Lineitems Include Edition Info', 'aea', 'label')), + ('INCLUDE_BIB_AUTHOR', + oils_i18n_gettext('INCLUDE_BIB_AUTHOR', + 'Order Lineitems Include Author Info', 'aea', 'label')), + ('INCLUDE_BIB_PAGINATION', + oils_i18n_gettext('INCLUDE_BIB_PAGINATION', + 'Order Lineitems Include Pagination Info', 'aea', 'label')), + ('COPY_SPEC_CODES', + oils_i18n_gettext('COPY_SPEC_CODES', + 'Order Lineitem Notes Include Copy Spec Codes', 'aea', 'label')), + ('INCLUDE_EMPTY_LI_NOTE', + oils_i18n_gettext('INCLUDE_EMPTY_LI_NOTE', + 'Order Lineitem Notes Always Present (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_CALL_NUMBER', + oils_i18n_gettext('INCLUDE_EMPTY_CALL_NUMBER', + 'Order Copies Always Include Call Number (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_ITEM_TYPE', + oils_i18n_gettext('INCLUDE_EMPTY_ITEM_TYPE', + 'Order Copies Always Include Item Type (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_LOCATION', + oils_i18n_gettext('INCLUDE_EMPTY_LOCATION', + 'Order Copies Always Include Location (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_COLLECTION_CODE', + oils_i18n_gettext('INCLUDE_EMPTY_COLLECTION_CODE', + 'Order Copies Always Include Collection Code (Even if Empty)', 'aea', 'label')), + ('LINEITEM_IDENT_VENDOR_NUMBER', + oils_i18n_gettext('LINEITEM_IDENT_VENDOR_NUMBER', + 'Lineitem Identifier Fields (LIN/PIA) Use Vendor-Encoded ID Value When Available', 'aea', 'label')), + ('LINEITEM_REF_ID_ONLY', + oils_i18n_gettext('LINEITEM_REF_ID_ONLY', + 'Lineitem Reference Feld (RFF) Uses Lineitem ID Only', 'aea', 'label')) + +; + +INSERT INTO acq.edi_attr_set (id, label) VALUES (1, 'Ingram Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (2, 'Baker & Taylor Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (3, 'Brodart Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (4, 'Midwest Tape Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (5, 'ULS Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (6, 'Recorded Books Default'); + +-- carve out space for mucho defaults +SELECT SETVAL('acq.edi_attr_set_id_seq'::TEXT, 1000); + +INSERT INTO acq.edi_attr_set_map (attr_set, attr) VALUES + + -- Ingram + (1, 'INCLUDE_PO_NAME'), + (1, 'INCLUDE_COPIES'), + (1, 'INCLUDE_ITEM_TYPE'), + (1, 'INCLUDE_COLLECTION_CODE'), + (1, 'INCLUDE_OWNING_LIB'), + (1, 'INCLUDE_QUANTITY'), + (1, 'INCLUDE_BIB_PAGINATION'), + + -- B&T + (2, 'INCLUDE_COPIES'), + (2, 'INCLUDE_ITEM_TYPE'), + (2, 'INCLUDE_COLLECTION_CODE'), + (2, 'INCLUDE_CALL_NUMBER'), + (2, 'INCLUDE_OWNING_LIB'), + (2, 'INCLUDE_QUANTITY'), + (2, 'INCLUDE_BIB_PAGINATION'), + (2, 'BUYER_ID_INCLUDE_VENDCODE'), + (2, 'INCLUDE_EMPTY_LI_NOTE'), + (2, 'INCLUDE_EMPTY_CALL_NUMBER'), + (2, 'INCLUDE_EMPTY_ITEM_TYPE'), + (2, 'INCLUDE_EMPTY_COLLECTION_CODE'), + (2, 'INCLUDE_EMPTY_LOCATION'), + (2, 'LINEITEM_IDENT_VENDOR_NUMBER'), + (2, 'LINEITEM_REF_ID_ONLY'), + + -- Brodart + (3, 'INCLUDE_COPIES'), + (3, 'INCLUDE_FUND'), + (3, 'INCLUDE_ITEM_TYPE'), + (3, 'INCLUDE_COLLECTION_CODE'), + (3, 'INCLUDE_OWNING_LIB'), + (3, 'INCLUDE_QUANTITY'), + (3, 'INCLUDE_BIB_PAGINATION'), + (3, 'COPY_SPEC_CODES'), + + -- Midwest + (4, 'INCLUDE_COPIES'), + (4, 'INCLUDE_FUND'), + (4, 'INCLUDE_OWNING_LIB'), + (4, 'INCLUDE_QUANTITY'), + (4, 'INCLUDE_BIB_PAGINATION'), + + -- ULS + (5, 'INCLUDE_COPIES'), + (5, 'INCLUDE_ITEM_TYPE'), + (5, 'INCLUDE_COLLECTION_CODE'), + (5, 'INCLUDE_OWNING_LIB'), + (5, 'INCLUDE_QUANTITY'), + (5, 'INCLUDE_BIB_AUTHOR'), + (5, 'INCLUDE_BIB_EDITION'), + (5, 'INCLUDE_EMPTY_LI_NOTE'), + + -- Recorded Books + (6, 'INCLUDE_COPIES'), + (6, 'INCLUDE_ITEM_TYPE'), + (6, 'INCLUDE_COLLECTION_CODE'), + (6, 'INCLUDE_OWNING_LIB'), + (6, 'INCLUDE_QUANTITY'), + (6, 'INCLUDE_BIB_PAGINATION') +; + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.edi_attr_set.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.edi_attr_set.sql new file mode 100644 index 0000000000..be1aa23943 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.edi_attr_set.sql @@ -0,0 +1,31 @@ +BEGIN; + +-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +CREATE TABLE acq.edi_attr ( + key TEXT PRIMARY KEY, + label TEXT NOT NULL UNIQUE +); + +CREATE TABLE acq.edi_attr_set ( + id SERIAL PRIMARY KEY, + label TEXT NOT NULL UNIQUE +); + +CREATE TABLE acq.edi_attr_set_map ( + id SERIAL PRIMARY KEY, + attr_set INTEGER NOT NULL REFERENCES acq.edi_attr_set(id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + attr TEXT NOT NULL REFERENCES acq.edi_attr(key) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + CONSTRAINT edi_attr_set_map_attr_once UNIQUE (attr_set, attr) +); + +-- An attr_set is not strictly required, since some edi_accounts/vendors +-- may not need to apply any attributes. +ALTER TABLE acq.edi_account ADD COLUMN attr_set + INTEGER REFERENCES acq.edi_attr_set(id); + +COMMIT; + + diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.data.edi_attr_set.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.data.edi_attr_set.sql new file mode 100644 index 0000000000..7bb42f6ec3 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.data.edi_attr_set.sql @@ -0,0 +1,155 @@ +BEGIN; + +-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT INTO acq.edi_attr (key, label) VALUES + ('INCLUDE_PO_NAME', + oils_i18n_gettext('INCLUDE_PO_NAME', + 'Oders Include PO Name', 'aea', 'label')), + ('INCLUDE_COPIES', + oils_i18n_gettext('INCLUDE_COPIES', + 'Orders Include Copy Data', 'aea', 'label')), + ('INCLUDE_FUND', + oils_i18n_gettext('INCLUDE_FUND', + 'Orders Include Copy Funds', 'aea', 'label')), + ('INCLUDE_CALL_NUMBER', + oils_i18n_gettext('INCLUDE_CALL_NUMBER', + 'Orders Include Copy Call Numbers', 'aea', 'label')), + ('INCLUDE_ITEM_TYPE', + oils_i18n_gettext('INCLUDE_ITEM_TYPE', + 'Orders Include Copy Item Types', 'aea', 'label')), + ('INCLUDE_ITEM_BARCODE', + oils_i18n_gettext('INCLUDE_ITEM_BARCODE', + 'Orders Include Copy Barcodes', 'aea', 'label')), + ('INCLUDE_LOCATION', + oils_i18n_gettext('INCLUDE_LOCATION', + 'Orders Include Copy Locations', 'aea', 'label')), + ('INCLUDE_COLLECTION_CODE', + oils_i18n_gettext('INCLUDE_COLLECTION_CODE', + 'Orders Include Copy Collection Codes', 'aea', 'label')), + ('INCLUDE_OWNING_LIB', + oils_i18n_gettext('INCLUDE_OWNING_LIB', + 'Orders Include Copy Owning Library', 'aea', 'label')), + ('INCLUDE_QUANTITY', + oils_i18n_gettext('INCLUDE_QUANTITY', + 'Orders Include Copy Quantities', 'aea', 'label')), + ('INCLUDE_COPY_ID', + oils_i18n_gettext('INCLUDE_COPY_ID', + 'Orders Include Copy IDs', 'aea', 'label')), + ('BUYER_ID_INCLUDE_VENDCODE', + oils_i18n_gettext('BUYER_ID_INCLUDE_VENDCODE', + 'Buyer ID Qualifier Includes Vendcode', 'aea', 'label')), + ('INCLUDE_BIB_EDITION', + oils_i18n_gettext('INCLUDE_BIB_EDITION', + 'Order Lineitems Include Edition Info', 'aea', 'label')), + ('INCLUDE_BIB_AUTHOR', + oils_i18n_gettext('INCLUDE_BIB_AUTHOR', + 'Order Lineitems Include Author Info', 'aea', 'label')), + ('INCLUDE_BIB_PAGINATION', + oils_i18n_gettext('INCLUDE_BIB_PAGINATION', + 'Order Lineitems Include Pagination Info', 'aea', 'label')), + ('COPY_SPEC_CODES', + oils_i18n_gettext('COPY_SPEC_CODES', + 'Order Lineitem Notes Include Copy Spec Codes', 'aea', 'label')), + ('INCLUDE_EMPTY_LI_NOTE', + oils_i18n_gettext('INCLUDE_EMPTY_LI_NOTE', + 'Order Lineitem Notes Always Present (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_CALL_NUMBER', + oils_i18n_gettext('INCLUDE_EMPTY_CALL_NUMBER', + 'Order Copies Always Include Call Number (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_ITEM_TYPE', + oils_i18n_gettext('INCLUDE_EMPTY_ITEM_TYPE', + 'Order Copies Always Include Item Type (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_LOCATION', + oils_i18n_gettext('INCLUDE_EMPTY_LOCATION', + 'Order Copies Always Include Location (Even if Empty)', 'aea', 'label')), + ('INCLUDE_EMPTY_COLLECTION_CODE', + oils_i18n_gettext('INCLUDE_EMPTY_COLLECTION_CODE', + 'Order Copies Always Include Collection Code (Even if Empty)', 'aea', 'label')), + ('LINEITEM_IDENT_VENDOR_NUMBER', + oils_i18n_gettext('LINEITEM_IDENT_VENDOR_NUMBER', + 'Lineitem Identifier Fields (LIN/PIA) Use Vendor-Encoded ID Value When Available', 'aea', 'label')), + ('LINEITEM_REF_ID_ONLY', + oils_i18n_gettext('LINEITEM_REF_ID_ONLY', + 'Lineitem Reference Feld (RFF) Uses Lineitem ID Only', 'aea', 'label')) + +; + +INSERT INTO acq.edi_attr_set (id, label) VALUES (1, 'Ingram Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (2, 'Baker & Taylor Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (3, 'Brodart Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (4, 'Midwest Tape Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (5, 'ULS Default'); +INSERT INTO acq.edi_attr_set (id, label) VALUES (6, 'Recorded Books Default'); + +-- carve out space for mucho defaults +SELECT SETVAL('acq.edi_attr_set_id_seq'::TEXT, 1000); + +INSERT INTO acq.edi_attr_set_map (attr_set, attr) VALUES + + -- Ingram + (1, 'INCLUDE_PO_NAME'), + (1, 'INCLUDE_COPIES'), + (1, 'INCLUDE_ITEM_TYPE'), + (1, 'INCLUDE_COLLECTION_CODE'), + (1, 'INCLUDE_OWNING_LIB'), + (1, 'INCLUDE_QUANTITY'), + (1, 'INCLUDE_BIB_PAGINATION'), + + -- B&T + (2, 'INCLUDE_COPIES'), + (2, 'INCLUDE_ITEM_TYPE'), + (2, 'INCLUDE_COLLECTION_CODE'), + (2, 'INCLUDE_CALL_NUMBER'), + (2, 'INCLUDE_OWNING_LIB'), + (2, 'INCLUDE_QUANTITY'), + (2, 'INCLUDE_BIB_PAGINATION'), + (2, 'BUYER_ID_INCLUDE_VENDCODE'), + (2, 'INCLUDE_EMPTY_LI_NOTE'), + (2, 'INCLUDE_EMPTY_CALL_NUMBER'), + (2, 'INCLUDE_EMPTY_ITEM_TYPE'), + (2, 'INCLUDE_EMPTY_COLLECTION_CODE'), + (2, 'INCLUDE_EMPTY_LOCATION'), + (2, 'LINEITEM_IDENT_VENDOR_NUMBER'), + (2, 'LINEITEM_REF_ID_ONLY'), + + -- Brodart + (3, 'INCLUDE_COPIES'), + (3, 'INCLUDE_FUND'), + (3, 'INCLUDE_ITEM_TYPE'), + (3, 'INCLUDE_COLLECTION_CODE'), + (3, 'INCLUDE_OWNING_LIB'), + (3, 'INCLUDE_QUANTITY'), + (3, 'INCLUDE_BIB_PAGINATION'), + (3, 'COPY_SPEC_CODES'), + + -- Midwest + (4, 'INCLUDE_COPIES'), + (4, 'INCLUDE_FUND'), + (4, 'INCLUDE_OWNING_LIB'), + (4, 'INCLUDE_QUANTITY'), + (4, 'INCLUDE_BIB_PAGINATION'), + + -- ULS + (5, 'INCLUDE_COPIES'), + (5, 'INCLUDE_ITEM_TYPE'), + (5, 'INCLUDE_COLLECTION_CODE'), + (5, 'INCLUDE_OWNING_LIB'), + (5, 'INCLUDE_QUANTITY'), + (5, 'INCLUDE_BIB_AUTHOR'), + (5, 'INCLUDE_BIB_EDITION'), + (5, 'INCLUDE_EMPTY_LI_NOTE'), + + -- Recorded Books + (6, 'INCLUDE_COPIES'), + (6, 'INCLUDE_ITEM_TYPE'), + (6, 'INCLUDE_COLLECTION_CODE'), + (6, 'INCLUDE_OWNING_LIB'), + (6, 'INCLUDE_QUANTITY'), + (6, 'INCLUDE_BIB_PAGINATION') +; + + +COMMIT; + + diff --git a/Open-ILS/src/support-scripts/test-scripts/edi_writer.pl b/Open-ILS/src/support-scripts/test-scripts/edi_writer.pl new file mode 100755 index 0000000000..075f2f145e --- /dev/null +++ b/Open-ILS/src/support-scripts/test-scripts/edi_writer.pl @@ -0,0 +1,25 @@ +#!/usr/bin/perl +use strict; use warnings; +use OpenILS::Utils::EDIWriter; +require '../oils_header.pl'; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use Getopt::Long; + +my $config = '/openils/conf/opensrf_core.xml'; +my $po_id; + +GetOptions( + 'osrf-config' => \$config, + 'po-id=i' => \$po_id +); + + +osrf_connect($config); + +my $writer = OpenILS::Utils::EDIWriter->new({pretty => 1}); +#my $writer = OpenILS::Utils::EDIWriter->new; +my $edi = $writer->write($po_id); + +print "$edi\n"; + +