<field reporter:label="Replace Specification" name="replace_spec" reporter:datatype="text"/>
<field reporter:label="Remove Specification" name="strip_spec" reporter:datatype="text"/>
<field reporter:label="Preserve Specification" name="preserve_spec" reporter:datatype="text"/>
+ <field reporter:label="Min. Quality Ratio" name="lwm_ratio" reporter:datatype="float"/>
</fields>
<links>
<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
<field reporter:label="Import Item ID" name="id" reporter:datatype="id"/>
<field reporter:label="Import Record" name="record" reporter:datatype="link"/>
<field reporter:label="Attribute Definition" name="definition" reporter:datatype="link"/>
+ <field reporter:label="Import Error" name="import_error" reporter:datatype="link"/>
+ <field reporter:label="Import Error Detail" name="error_detail" reporter:datatype="text"/>
<field reporter:label="Owning Library" name="owning_lib" reporter:datatype="int"/>
<field reporter:label="Circulating Library" name="circ_lib" reporter:datatype="int"/>
<field reporter:label="Call Number" name="call_number" reporter:datatype="text"/>
<field reporter:label="OPAC Visible" name="opac_visible" reporter:datatype="bool"/>
</fields>
<links>
+ <link field="import_error" reltype="has_a" key="code" map="" class="vie"/>
<link field="record" reltype="has_a" key="id" map="" class="vqbr"/>
<link field="definition" reltype="has_a" key="id" map="" class="viiad"/>
</links>
<field reporter:label="Name" name="name" reporter:datatype="text" oils_persist:i18n="true"/>
<field reporter:label="Complete" name="complete" reporter:datatype="bool"/>
<field reporter:label="Type" name="queue_type" reporter:datatype="text"/>
+ <field reporter:label="Match Set" name="match_set" reporter:datatype="link"/>
<field reporter:label="Item Import Attribute Definition" name="item_attr_def" reporter:datatype="link"/>
</fields>
<links>
<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
<link field="item_attr_def" reltype="has_a" key="id" map="" class="viiad"/>
+ <link field="match_set" reltype="has_a" key="id" map="" class="vms"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
</permacrud>
</class>
+ <class id="vie" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="vandelay::import_error" oils_persist:tablename="vandelay.import_error" reporter:label="Import/Overlay Error Definitions">
+ <fields oils_persist:primary="code">
+ <field reporter:label="Error Code" name="code" reporter:selector="description" reporter:datatype="id"/>
+ <field reporter:label="Description" name="description" reporter:datatype="text" oils_persist:i18n="true"/>
+ </fields>
+ <links>
+ <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="item_attr_def" reltype="has_a" key="id" map="" class="viiad"/>
+ <link field="match_set" reltype="has_a" key="id" map="" class="vms"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <retrieve/>
+ </actions>
+ </permacrud>
+ </class>
+
<class id="vqbr" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="vandelay::queued_bib_record" oils_persist:tablename="vandelay.queued_bib_record" reporter:label="Queued Bib Record">
<fields oils_persist:primary="id" oils_persist:sequence="vandelay.queued_record_id_seq">
<field reporter:label="Record ID" name="id" reporter:datatype="id"/>
<field reporter:label="Queue" name="queue" reporter:datatype="link"/>
<field reporter:label="Bib Source" name="bib_source" reporter:datatype="link"/>
<field reporter:label="Final Target Record" name="imported_as" reporter:datatype="link"/>
+ <field reporter:label="Import Error" name="import_error" reporter:datatype="link"/>
+ <field reporter:label="Import Error Detail" name="error_detail" reporter:datatype="text"/>
<field reporter:label="Purpose" name="purpose" reporter:datatype="text"/>
<field reporter:label="Attributes" name="attributes" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Matches" name="matches" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field reporter:label="Import Items" name="import_items" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field reporter:label="Quality" name="quality" reporter:datatype="int"/>
</fields>
<links>
+ <link field="import_error" reltype="has_a" key="code" map="" class="vie"/>
<link field="queue" reltype="has_a" key="id" map="" class="vbq"/>
<link field="bib_source" reltype="has_a" key="id" map="" class="cbs"/>
<link field="imported_as" reltype="has_a" key="id" map="" class="bre"/>
<link field="attributes" reltype="has_many" key="record" map="" class="vqbra"/>
<link field="matches" reltype="has_many" key="queued_record" map="" class="vbm"/>
+ <link field="import_items" reltype="has_many" key="record" map="" class="vii"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
<field reporter:label="Description" name="description" reporter:datatype="text" oils_persist:i18n="true"/>
<field reporter:label="XPath" name="xpath" reporter:datatype="text"/>
<field reporter:label="Remove RegExp" name="remove" reporter:datatype="text"/>
- <field reporter:label="Is Identifier?" name="ident" reporter:datatype="bool"/>
</fields>
<links/>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<fields oils_persist:primary="id" oils_persist:sequence="vandelay.bib_match_id_seq">
<field reporter:label="Match ID" name="id" reporter:datatype="id"/>
<field reporter:label="Queued Record" name="queued_record" reporter:datatype="link"/>
- <field reporter:label="Matched Attribute" name="matched_attr" reporter:datatype="link"/>
<field reporter:label="Evergreen Record" name="eg_record" reporter:datatype="link"/>
- <field reporter:label="Field Type" name="field_type" reporter:datatype="text"/>
+ <field reporter:label="Quality" name="quality" reporter:datatype="text"/>
+ <field reporter:label="Match Score" name="match_score" reporter:datatype="text"/>
</fields>
<links>
<link field="queued_record" reltype="has_a" key="id" map="" class="vqbr"/>
<link field="eg_record" reltype="has_a" key="id" map="" class="bre"/>
- <link field="matched_attr" reltype="has_a" key="id" map="" class="vqbra"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
<field reporter:label="Name" name="name" reporter:datatype="text" oils_persist:i18n="true"/>
<field reporter:label="Complete" name="complete" reporter:datatype="bool"/>
<field reporter:label="Type" name="queue_type" reporter:datatype="text"/>
+ <field reporter:label="Match Set" name="match_set" reporter:datatype="link"/>
</fields>
<links>
<link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+ <link field="match_set" reltype="has_a" key="id" map="" class="vms"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
<field reporter:label="MARC" name="marc" reporter:datatype="text"/>
<field reporter:label="Queue" name="queue" reporter:datatype="link"/>
<field reporter:label="Final Target Record" name="imported_as" reporter:datatype="link"/>
+ <field reporter:label="Import Error" name="import_error" reporter:datatype="link"/>
+ <field reporter:label="Import Error Detail" name="error_detail" reporter:datatype="text"/>
<field reporter:label="Purpose" name="purpose" reporter:datatype="text"/>
<field reporter:label="Attributes" name="attributes" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Matches" name="matches" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field reporter:label="Quality" name="quality" reporter:datatype="int"/>
</fields>
<links>
+ <link field="import_error" reltype="has_a" key="code" map="" class="vie"/>
<link field="queue" reltype="has_a" key="id" map="" class="vaq"/>
<link field="imported_as" reltype="has_a" key="id" map="" class="are"/>
<link field="attributes" reltype="has_many" key="record" map="" class="vqara"/>
<field reporter:label="Description" name="description" reporter:datatype="text" oils_persist:i18n="true"/>
<field reporter:label="XPath" name="xpath" reporter:datatype="text"/>
<field reporter:label="Remove RegExp" name="remove" reporter:datatype="text"/>
- <field reporter:label="Is Identifier?" name="ident" reporter:datatype="bool"/>
</fields>
<links/>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<fields oils_persist:primary="id" oils_persist:sequence="vandelay.authority_match_id_seq">
<field reporter:label="Match ID" name="id" reporter:datatype="id"/>
<field reporter:label="Queued Record" name="queued_record" reporter:datatype="link"/>
- <field reporter:label="Matched Attribute" name="matched_attr" reporter:datatype="link"/>
<field reporter:label="Evergreen Record" name="eg_record" reporter:datatype="link"/>
+ <field reporter:label="Quality" name="quality" reporter:datatype="int"/>
</fields>
<links>
<link field="queued_record" reltype="has_a" key="id" map="" class="vqbr"/>
<link field="eg_record" reltype="has_a" key="id" map="" class="bre"/>
- <link field="matched_attr" reltype="has_a" key="id" map="" class="vqbra"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
</permacrud>
</class>
+ <class id="vms" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="vandelay::match_set" oils_persist:tablename="vandelay.match_set" reporter:label="Record Matching Definition Set">
+ <fields oils_persist:primary="id" oils_persist:sequence="vandelay.match_set_id_seq">
+ <field reporter:label="Match Set ID" name="id" reporter:datatype="id"/>
+ <field reporter:label="Name" name="name" reporter:datatype="text"/>
+ <field reporter:label="Owning Library" name="owner" reporter:datatype="link"/>
+ <field reporter:label="Match Set Type" name="mtype" reporter:datatype="text"/>
+ </fields>
+ <links>
+ <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_IMPORT_MATCH_SET" context_field="owner"/>
+ <retrieve permission="ADMIN_IMPORT_MATCH_SET" context_field="owner"/>
+ <update permission="ADMIN_IMPORT_MATCH_SET" context_field="owner"/>
+ <delete permission="ADMIN_IMPORT_MATCH_SET" context_field="owner"/>
+ </actions>
+ </permacrud>
+ </class>
+
+ <class id="vmsp" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="vandelay::match_set_point" oils_persist:tablename="vandelay.match_set_point" reporter:label="Record Matching Definition">
+ <fields oils_persist:primary="id" oils_persist:sequence="vandelay.match_set_point_id_seq">
+ <field reporter:label="Match Definition ID" name="id" reporter:datatype="id"/>
+ <field reporter:label="Expression Tree Parent" name="parent" reporter:datatype="link"/>
+ <field reporter:label="Match Set" name="match_set" reporter:datatype="link"/>
+ <field reporter:label="Boolean Operator" name="bool_op" reporter:datatype="text"/>
+ <field reporter:label="Coded Field" name="svf" reporter:datatype="link"/>
+ <field reporter:label="Tag" name="tag" reporter:datatype="text"/>
+ <field reporter:label="Subfield" name="subfield" reporter:datatype="text"/>
+ <field reporter:label="Negate" name="negate" reporter:datatype="bool"/>
+ <field reporter:label="Importance" name="quality" reporter:datatype="int"/>
+ <field reporter:label="Expression Tree Children" name="children" oils_persist:virtual="true" reporter:datatype="link"/>
+ </fields>
+ <links>
+ <link field="parent" reltype="has_a" key="id" map="" class="vmsp"/>
+ <link field="match_set" reltype="has_a" key="id" map="" class="vms"/>
+ <link field="svf" reltype="has_a" key="id" map="" class="crad"/>
+ <link field="children" reltype="has_many" key="parent" map="" class="vmsp"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </create>
+ <retrieve permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </retrieve>
+ <update permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </update>
+ <delete permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </delete>
+ </actions>
+ </permacrud>
+ </class>
+
+ <class id="vmsq" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="vandelay::match_set_quality" oils_persist:tablename="vandelay.match_set_quality" reporter:label="Record Quality Metric">
+ <fields oils_persist:primary="id" oils_persist:sequence="vandelay.match_set_quality_id_seq">
+ <field reporter:label="Quality Metric ID" name="id" reporter:datatype="id"/>
+ <field reporter:label="Match Set" name="match_set" reporter:datatype="link"/>
+ <field reporter:label="Record Attribute" name="svf" reporter:datatype="text"/>
+ <field reporter:label="Tag" name="tag" reporter:datatype="text"/>
+ <field reporter:label="Subfield" name="subfield" reporter:datatype="text"/>
+ <field reporter:label="Value" name="value" reporter:datatype="text"/>
+ <field reporter:label="Quality" name="quality" reporter:datatype="int"/>
+ </fields>
+ <links>
+ <link field="match_set" reltype="has_a" key="id" map="" class="vms"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </create>
+ <retrieve permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </retrieve>
+ <update permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </update>
+ <delete permission="ADMIN_IMPORT_MATCH_SET">
+ <context link="match_set" field="owner"/>
+ </delete>
+ </actions>
+ </permacrud>
+ </class>
<class id="auoi" controller="open-ils.cstore" oils_obj:fieldmapper="actor::usr_org_unit_opt_in" oils_persist:tablename="actor.usr_org_unit_opt_in" reporter:label="User Sharing Opt-in">
<fields oils_persist:primary="id" oils_persist:sequence="actor.usr_org_unit_opt_in_id_seq">
}
return;
},
+
+ get_queued_bib_attr => sub {
+ my $name = shift or return; # the first arg is always the name
+ my ($attr) = @_;
+ # use Data::Dumper; $logger->warn("get_queued_bib_attr: " . Dumper($attr));
+ ($name and @$attr) or return;
+
+ my $query = {
+ select => {'vqbrad' => ['id']},
+ from => 'vqbrad',
+ where => {code => $name}
+ };
+
+ my $def_ids = new_editor()->json_query($query);
+ @$def_ids or return;
+
+ my $length;
+ $name =~ s/^(\D+)_(\d+)$/$1/ and $length = $2;
+ foreach (@$attr) {
+ $_->field eq @{$def_ids}[0]->{id} or next;
+ next if $length and $length != length($_->attr_value);
+ return $_->attr_value;
+ }
+ return;
+ },
+
+ get_queued_auth_attr => sub {
+ my $name = shift or return; # the first arg is always the name
+ my ($attr) = @_;
+ # use Data::Dumper; $logger->warn("get_queued_auth_attr: " . Dumper($attr));
+ ($name and @$attr) or return;
+
+ my $query = {
+ select => {'vqarad' => ['id']},
+ from => 'vqarad',
+ where => {code => $name}
+ };
+
+ my $def_ids = new_editor()->json_query($query);
+ @$def_ids or return;
+
+ my $length;
+ $name =~ s/^(\D+)_(\d+)$/$1/ and $length = $2;
+ foreach (@$attr) {
+ $_->field eq @{$def_ids}[0]->{id} or next;
+ next if $length and $length != length($_->attr_value);
+ return $_->attr_value;
+ }
+ return;
+ },
+
};
my $name = shift;
my $owner = shift;
my $type = shift;
+ my $match_set = shift;
my $import_def = shift;
my $e = new_editor(authtoken => $auth, xact => 1);
$queue->owner( $owner );
$queue->queue_type( $type ) if ($type);
$queue->item_attr_def( $import_def ) if ($import_def);
+ $queue->match_set($match_set) if $match_set;
my $new_q = $e->create_vandelay_bib_queue( $queue );
return $e->die_event unless ($new_q);
my $name = shift;
my $owner = shift;
my $type = shift;
+ my $match_set = shift;
my $e = new_editor(authtoken => $auth, xact => 1);
stream => 1,
record_type => 'bib'
);
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.bib_queue.records.retrieve.export.print",
+ method => 'retrieve_queued_records',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ record_type => 'bib'
+);
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.bib_queue.records.retrieve.export.csv",
+ method => 'retrieve_queued_records',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ record_type => 'bib'
+);
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.bib_queue.records.retrieve.export.email",
+ method => 'retrieve_queued_records',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ record_type => 'bib'
+);
+
__PACKAGE__->register_method(
api_name => "open-ils.vandelay.auth_queue.records.retrieve",
method => 'retrieve_queued_records',
stream => 1,
record_type => 'auth'
);
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.auth_queue.records.retrieve.export.print",
+ method => 'retrieve_queued_records',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ record_type => 'auth'
+);
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.auth_queue.records.retrieve.export.csv",
+ method => 'retrieve_queued_records',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ record_type => 'auth'
+);
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.auth_queue.records.retrieve.export.email",
+ method => 'retrieve_queued_records',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ record_type => 'auth'
+);
__PACKAGE__->register_method(
api_name => "open-ils.vandelay.bib_queue.records.matches.retrieve",
signature => {
desc => q/Only retrieve queued authority records that have matches against existing records/
}
-
);
sub retrieve_queued_records {
my($self, $conn, $auth, $queue_id, $options) = @_;
- my $e = new_editor(authtoken => $auth, xact => 1);
- return $e->die_event unless $e->checkauth;
+
$options ||= {};
my $limit = $$options{limit} || 20;
my $offset = $$options{offset} || 0;
-
my $type = $self->{record_type};
+
+ my $e = new_editor(authtoken => $auth, xact => 1);
+ return $e->die_event unless $e->checkauth;
+
my $queue;
if($type eq 'bib') {
$queue = $e->retrieve_vandelay_bib_queue($queue_id) or return $e->die_event;
return $evt if ($evt);
my $class = ($type eq 'bib') ? 'vqbr' : 'vqar';
- my $search = ($type eq 'bib') ?
- 'search_vandelay_queued_bib_record' : 'search_vandelay_queued_authority_record';
+ my $query = {
+ select => {$class => ['id']},
+ from => $class,
+ where => {queue => $queue_id},
+ distinct => 1,
+ order_by => {$class => ['id']},
+ limit => $limit,
+ offset => $offset,
+ };
+ if($self->api_name =~ /export/) {
+ delete $query->{limit};
+ delete $query->{offset};
+ }
+
+ $query->{where}->{import_time} = undef if $$options{non_imported};
+
+ if($$options{with_import_error}) {
+
+ $query->{from} = {$class => {vii => {type => 'right'}}};
+ $query->{where}->{'-or'} = [
+ {'+vqbr' => {import_error => {'!=' => undef}}},
+ {'+vii' => {import_error => {'!=' => undef}}}
+ ];
+
+ } else {
+
+ if($$options{with_rec_import_error}) {
+ $query->{where}->{import_error} = {'!=' => undef};
+
+ } elsif( $$options{with_item_import_error} and $type eq 'bib') {
+
+ $query->{from} = {$class => 'vii'};
+ $query->{where}->{'+vii'} = {import_error => {'!=' => undef}};
+ }
+ }
+
+ if($self->api_name =~ /matches/) {
+ # find only records that have matches
+ my $mclass = $type eq 'bib' ? 'vbm' : 'vam';
+ $query->{from} = {$class => {$mclass => {type => 'right'}}};
+ }
+
+ my $record_ids = $e->json_query($query);
+
my $retrieve = ($type eq 'bib') ?
'retrieve_vandelay_queued_bib_record' : 'retrieve_vandelay_queued_authority_record';
+ my $search = ($type eq 'bib') ?
+ 'search_vandelay_queued_bib_record' : 'search_vandelay_queued_authority_record';
- my $filter = ($$options{non_imported}) ? {import_time => undef} : {};
+ if ($self->api_name =~ /export/) {
+ my $rec_list = $e->$search({id => [map { $_->{id} } @$record_ids]});
+ if ($self->api_name =~ /print/) {
- my $record_ids;
- if($self->api_name =~ /matches/) {
- # fetch only matched records
- $record_ids = queued_records_with_matches($e, $type, $queue_id, $limit, $offset, $filter);
+ $e->rollback;
+ return $U->fire_object_event(
+ undef,
+ 'vandelay.queued_'.$type.'_record.print',
+ $rec_list,
+ $e->requestor->ws_ou
+ );
+
+ } elsif ($self->api_name =~ /csv/) {
+
+ $e->rollback;
+ return $U->fire_object_event(
+ undef,
+ 'vandelay.queued_'.$type.'_record.csv',
+ $rec_list,
+ $e->requestor->ws_ou
+ );
+
+ } elsif ($self->api_name =~ /email/) {
+
+ $conn->respond_complete(1);
+
+ for my $rec (@$rec_list) {
+ $U->create_events_for_hook(
+ 'vandelay.queued_'.$type.'_record.email',
+ $rec,
+ $e->requestor->home_ou,
+ undef,
+ undef,
+ 1
+ );
+ }
+
+ }
} else {
- # fetch all queue records
- $record_ids = $e->$search([
- {queue => $queue_id, %$filter},
- {order_by => {$class => 'id'}, limit => $limit, offset => $offset}
- ],
- {idlist => 1}
- );
+ for my $rec_id (@$record_ids) {
+ my $flesh = ['attributes', 'matches'];
+ push(@$flesh, 'import_items') if $$options{flesh_import_items};
+ my $params = {flesh => 1, flesh_fields => {$class => $flesh}};
+ my $rec = $e->$retrieve([$rec_id->{id}, $params]);
+ $rec->clear_marc if $$options{clear_marc};
+ $conn->respond($rec);
+ }
}
+ $e->rollback;
+ return undef;
+}
- for my $rec_id (@$record_ids) {
- my $params = {
- flesh => 1,
- flesh_fields => {$class => ['attributes', 'matches']},
- };
- my $rec = $e->$retrieve([$rec_id, $params]);
- $rec->clear_marc if $$options{clear_marc};
- $conn->respond($rec);
+__PACKAGE__->register_method(
+ api_name => 'open-ils.vandelay.import_item.queue.retrieve',
+ method => 'retrieve_queue_import_items',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ authoritative => 1,
+ signature => q/
+ Returns Import Item (vii) objects for the selected queue.
+ Filter options:
+ with_import_error : only return items that failed to import
+ /
+);
+__PACKAGE__->register_method(
+ api_name => 'open-ils.vandelay.import_item.queue.export.print',
+ method => 'retrieve_queue_import_items',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ authoritative => 1,
+ signature => q/
+ Returns template-generated printable output of Import Item (vii) objects for the selected queue.
+ Filter options:
+ with_import_error : only return items that failed to import
+ /
+);
+__PACKAGE__->register_method(
+ api_name => 'open-ils.vandelay.import_item.queue.export.csv',
+ method => 'retrieve_queue_import_items',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ authoritative => 1,
+ signature => q/
+ Returns template-generated CSV output of Import Item (vii) objects for the selected queue.
+ Filter options:
+ with_import_error : only return items that failed to import
+ /
+);
+__PACKAGE__->register_method(
+ api_name => 'open-ils.vandelay.import_item.queue.export.email',
+ method => 'retrieve_queue_import_items',
+ api_level => 1,
+ argc => 2,
+ stream => 1,
+ authoritative => 1,
+ signature => q/
+ Emails template-generated output of Import Item (vii) objects for the selected queue.
+ Filter options:
+ with_import_error : only return items that failed to import
+ /
+);
+
+sub retrieve_queue_import_items {
+ my($self, $conn, $auth, $q_id, $options) = @_;
+
+ $options ||= {};
+ my $limit = $$options{limit} || 20;
+ my $offset = $$options{offset} || 0;
+
+ my $e = new_editor(authtoken => $auth);
+ return $e->event unless $e->checkauth;
+
+ my $queue = $e->retrieve_vandelay_bib_queue($q_id) or return $e->event;
+ my $evt = check_queue_perms($e, 'bib', $queue);
+ return $evt if $evt;
+
+ my $query = {
+ select => {vii => ['id']},
+ from => {
+ vii => {
+ vqbr => {
+ join => {
+ 'vbq' => {
+ field => 'id',
+ fkey => 'queue',
+ filter => {id => $q_id}
+ }
+ }
+ }
+ }
+ },
+ order_by => {'vii' => ['record','id']},
+ limit => $limit,
+ offset => $offset
+ };
+ if($self->api_name =~ /export/) {
+ delete $query->{limit};
+ delete $query->{offset};
}
- $e->rollback;
+
+ $query->{where} = {'+vii' => {import_error => {'!=' => undef}}}
+ if $$options{with_import_error};
+
+ my $items = $e->json_query($query);
+ my $item_list = $e->search_vandelay_import_item({id => [map { $_->{id} } @$items]});
+ if ($self->api_name =~ /export/) {
+ if ($self->api_name =~ /print/) {
+
+ return $U->fire_object_event(
+ undef,
+ 'vandelay.import_items.print',
+ $item_list,
+ $e->requestor->ws_ou
+ );
+
+ } elsif ($self->api_name =~ /csv/) {
+
+ return $U->fire_object_event(
+ undef,
+ 'vandelay.import_items.csv',
+ $item_list,
+ $e->requestor->ws_ou
+ );
+
+ } elsif ($self->api_name =~ /email/) {
+
+ $conn->respond_complete(1);
+
+ for my $item (@$item_list) {
+ $U->create_events_for_hook(
+ 'vandelay.import_items.email',
+ $item,
+ $e->requestor->home_ou,
+ undef,
+ undef,
+ 1
+ );
+ }
+
+ }
+ } else {
+ for my $item (@$item_list) {
+ $conn->respond($item);
+ }
+ }
+
return undef;
}
return $e->die_event unless $e->checkauth;
$args ||= {};
my $err = import_record_list_impl($self, $conn, $rec_ids, $e->requestor, $args);
- $e->rollback;
+ try {$e->rollback} otherwise {};
return $err if $err;
return {complete => 1};
}
return [ map {$_->{queued_record}} @$data ];
}
+
sub import_record_list_impl {
my($self, $conn, $rec_ids, $requestor, $args) = @_;
my $overlay_map = $args->{overlay_map} || {};
my $type = $self->{record_type};
- my $total = @$rec_ids;
- my $count = 0;
my %queues;
- my $step = 1;
+ my $report_args = {
+ progress => 1,
+ step => 1,
+ conn => $conn,
+ total => scalar(@$rec_ids),
+ report_all => $$args{report_all}
+ };
my $auto_overlay_exact = $$args{auto_overlay_exact};
my $auto_overlay_1match = $$args{auto_overlay_1match};
+ my $auto_overlay_best = $$args{auto_overlay_best_match};
+ my $match_quality_ratio = $$args{match_quality_ratio};
my $merge_profile = $$args{merge_profile};
my $bib_source = $$args{bib_source};
- my $report_all = $$args{report_all};
+ my $import_no_match = $$args{import_no_match};
my $overlay_func = 'vandelay.overlay_bib_record';
my $auto_overlay_func = 'vandelay.auto_overlay_bib_record';
+ my $auto_overlay_best_func = 'vandelay.auto_overlay_bib_record_with_best'; # XXX bib-only
my $retrieve_func = 'retrieve_vandelay_queued_bib_record';
my $update_func = 'update_vandelay_queued_bib_record';
my $search_func = 'search_vandelay_queued_bib_record';
my $update_queue_func = 'update_vandelay_bib_queue';
my $rec_class = 'vqbr';
- my %bib_sources;
my $editor = new_editor();
- my $sources = $editor->search_config_bib_source({id => {'!=' => undef}});
- foreach my $src (@$sources) {
- $bib_sources{$src->id} = $src->source;
- }
+ my %bib_sources;
+ my $sources = $editor->search_config_bib_source({id => {'!=' => undef}});
+ $bib_sources{$_->id} = $_->source for @$sources;
if($type eq 'auth') {
$overlay_func =~ s/bib/auth/o;
my @success_rec_ids;
for my $rec_id (@$rec_ids) {
+ my $error = 0;
my $overlay_target = $overlay_map->{$rec_id};
- my $error = 0;
my $e = new_editor(xact => 1);
$e->requestor($requestor);
+ $$report_args{e} = $e;
+ $$report_args{import_error} = undef;
+ $$report_args{evt} = undef;
+
my $rec = $e->$retrieve_func([
$rec_id,
{ flesh => 1,
]);
unless($rec) {
- $conn->respond({total => $total, progress => ++$count, imported => $rec_id, err_event => $e->event});
- $e->rollback;
+ $$report_args{evt} = $e->event;
+ finish_rec_import_attempt($report_args);
next;
}
+ $$report_args{rec} = $rec;
+
if($rec->import_time) {
$e->rollback;
next;
if($res->{$auto_overlay_func} eq 't') {
$logger->info("vl: $type auto-overlay succeeded for queued rec " . $rec->id);
$imported = 1;
+ } else {
+ $logger->info("vl: $type auto-overlay failed for queued rec " . $rec->id);
}
} else {
}
}
- if(!$imported and !$error) {
+ if(!$imported and !$error and $auto_overlay_best and scalar(@{$rec->matches}) > 0 ) {
+
+ # caller says to overlay the best match
+
+ my $res = $e->json_query(
+ {
+ from => [
+ $auto_overlay_best_func,
+ $rec->id,
+ $merge_profile,
+ $match_quality_ratio
+ ]
+ }
+ );
+
+ if($res and ($res = $res->[0])) {
+
+ if($res->{$auto_overlay_best_func} eq 't') {
+ $logger->info("vl: $type auto-overlay-best succeeded for queued rec " . $rec->id);
+ $imported = 1;
+ } else {
+ $$report_args{import_error} = 'overlay.record.quality' if $match_quality_ratio > 0;
+ $logger->info("vl: $type auto-overlay-best failed for queued rec " . $rec->id);
+ }
+
+ } else {
+ $error = 1;
+ $logger->error("vl: Error attempting overlay with func=$auto_overlay_best_func, ".
+ "quality_ratio=$match_quality_ratio, profile=$merge_profile, record=$rec_id");
+ }
+ }
+
+ if(!$imported and !$error and $import_no_match and scalar(@{$rec->matches}) == 0) {
# No overlay / merge occurred. Do a traditional record import by creating a new record
+ $logger->info("vl: creating new $type record for queued record $rec_id");
if($type eq 'bib') {
$record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import($e, $rec->marc, $bib_sources{$rec->bib_source});
} else {
}
if($U->event_code($record)) {
-
- $e->event($record);
- $e->rollback;
+ $$report_args{import_error} = 'import.duplicate.tcn' if $record->{textcode} eq 'TCN_EXISTS';
+ $$report_args{evt} = $record;
} else {
$logger->info("vl: successfully imported new $type record");
$rec->imported_as($record->id);
$rec->import_time('now');
-
+ $rec->clear_import_error;
+ $rec->clear_error_detail;
$imported = 1 if $e->$update_func($rec);
}
}
if($imported) {
push @success_rec_ids, $rec_id;
- $e->commit;
+ finish_rec_import_attempt($report_args);
+
} else {
# Send an update whenever there's an error
- $conn->respond({total => $total, progress => ++$count, imported => $rec_id, err_event => $e->event});
- }
-
- if($report_all or (++$count % $step) == 0) {
- $conn->respond({total => $total, progress => $count, imported => $rec_id});
- # report often at first, climb quickly, then hold steady
- $step *= 2 unless $step == 256;
+ $$report_args{evt} = $e->event unless $$report_args{evt};
+ finish_rec_import_attempt($report_args);
}
}
$e->rollback;
}
+ # import the copies
import_record_asset_list_impl($conn, \@success_rec_ids, $requestor);
- $conn->respond({total => $total, progress => $count});
+ $conn->respond({total => $$report_args{total}, progress => $$report_args{progress}});
return undef;
}
+# tracks any import errors, commits the current xact, responds to the client
+sub finish_rec_import_attempt {
+ my $args = shift;
+ my $evt = $$args{evt};
+ my $rec = $$args{rec};
+ my $e = $$args{e};
+
+ my $error = $$args{import_error};
+ $error = 'general.unknown' if $evt and not $error;
+
+ # error tracking
+ if($rec) {
+
+ if($error or $evt) {
+ # failed import
+ # since an error occurred, there's no guarantee the transaction wasn't
+ # rolled back. force a rollback and create a new editor.
+ $e->rollback;
+ $e = new_editor(xact => 1);
+ $rec->import_error($error);
+
+ if($evt) {
+ my $detail = sprintf("%s : %s", $evt->{textcode}, substr($evt->{desc}, 0, 140));
+ $rec->error_detail($detail);
+ }
+
+ my $method = 'update_vandelay_queued_bib_record';
+ $method =~ s/bib/authority/ if $$args{type} eq 'auth';
+ $e->$method($rec) and $e->commit or $e->rollback;
+
+ } else {
+ # commit the successful import
+ $e->commit;
+ }
+
+ } else {
+ # requested queued record was not found
+ $e->rollback;
+ }
+
+ # respond to client
+ if($$args{report_all} or ($$args{progress} % $$args{step}) == 0) {
+ $$args{conn}->respond({
+ total => $$args{total},
+ progress => $$args{progress},
+ imported => ($rec) ? $rec->id : undef,
+ err_event => $evt
+ });
+ $$args{step} *= 2 unless $$args{step} == 256;
+ }
+
+ $$args{progress}++;
+}
+
+
+
+
__PACKAGE__->register_method(
api_name => "open-ils.vandelay.bib_queue.owner.retrieve",
my $search = 'search_vandelay_queued_bib_record';
$search =~ s/bib/authority/ if $type ne 'bib';
- return {
+ my $summary = {
queue => $queue,
total => scalar(@{$e->$search({queue => $queue_id}, {idlist=>1})}),
imported => scalar(@{$e->$search({queue => $queue_id, import_time => {'!=' => undef}}, {idlist=>1})}),
};
+
+ my $class = ($type eq 'bib') ? 'vqbr' : 'vqar';
+ $summary->{rec_import_errors} = $e->json_query({
+ select => {$class => [{alias => 'count', column => 'id', transform => 'count', aggregate => 1}]},
+ from => $class,
+ where => {queue => $queue_id, import_error => {'!=' => undef}}
+ })->[0]->{count};
+
+ if($type eq 'bib') {
+
+ my $query = {
+ select => {vii => [{alias => 'count', column => 'id', transform => 'count', aggregate => 1}]},
+ from => 'vii',
+ where => {
+ record => {
+ in => {
+ select => {vqbr => ['id']},
+ from => 'vqbr',
+ where => {queue => $queue_id}
+ }
+ }
+ }
+ };
+
+ $summary->{total_items} = $e->json_query($query)->[0]->{count};
+ $query->{where}->{import_error} = {'!=' => undef};
+ $summary->{item_import_errors} = $e->json_query($query)->[0]->{count};
+ }
+
+ return $summary;
}
# --------------------------------------------------------------------------------
sub import_record_asset_list_impl {
my($conn, $rec_ids, $requestor) = @_;
- my $total = @$rec_ids;
- my $try_count = 0;
- my $in_count = 0;
my $roe = new_editor(xact=> 1, requestor => $requestor);
+ # for speed, filter out any records have not been
+ # imported or have no import items to load
+ $rec_ids = $roe->json_query({
+ select => {vqbr => ['id']},
+ from => {vqbr => 'vii'},
+ where => {'+vqbr' => {import_time => {'!=' => undef}}},
+ distinct => 1
+ });
+ $rec_ids = [map {$_->{id}} @$rec_ids];
+
+ my $report_args = {
+ conn => $conn,
+ total => scalar(@$rec_ids),
+ step => 1, # how often to respond
+ progress => 1,
+ in_count => 0,
+ };
+
for my $rec_id (@$rec_ids) {
my $rec = $roe->retrieve_vandelay_queued_bib_record($rec_id);
- next unless $rec and $rec->import_time;
my $item_ids = $roe->search_vandelay_import_item({record => $rec->id}, {idlist=>1});
for my $item_id (@$item_ids) {
my $e = new_editor(requestor => $requestor, xact => 1);
my $item = $e->retrieve_vandelay_import_item($item_id);
- $try_count++;
+ $$report_args{import_item} = $item;
+ $$report_args{e} = $e;
+ $$report_args{import_error} = undef;
+ $$report_args{evt} = undef;
# --------------------------------------------------------------------------------
# Find or create the volume
$e, $item->call_number, $rec->imported_as, $item->owning_lib);
if($evt) {
- respond_with_status($conn, $total, $try_count, $in_count, $evt);
+
+ $$report_args{evt} = $evt;
+ respond_with_status($report_args);
next;
}
# --------------------------------------------------------------------------------
# see if a valid circ_modifier was provided
# --------------------------------------------------------------------------------
- #if($copy->circ_modifier and not $e->retrieve_config_circ_modifier($item->circ_modifier)) {
if($copy->circ_modifier and not $e->search_config_circ_modifier({code=>$item->circ_modifier})->[0]) {
- respond_with_status($conn, $total, $try_count, $in_count, $e->die_event);
+ $$report_args{evt} = $e->die_event;
+ $$report_args{import_error} = 'import.item.invalid.circ_modifier';
+ respond_with_status($report_args);
+ next;
+ }
+
+ if($copy->location and not $e->retrieve_asset_copy_location($copy->location)) {
+ $$report_args{evt} = $e->die_event;
+ $$report_args{import_error} = 'import.item.invalid.location';
+ respond_with_status($report_args);
next;
}
if($evt = OpenILS::Application::Cat::AssetCommon->create_copy($e, $vol, $copy)) {
- try { $e->rollback } otherwise {}; # sometimes calls die_event, sometimes not
- respond_with_status($conn, $total, $try_count, $in_count, $evt);
+ $$report_args{evt} = $evt;
+ $$report_args{import_error} = 'import.item.duplicate.barcode'
+ if $evt->{textcode} eq 'ITEM_BARCODE_EXISTS';
+ respond_with_status($report_args);
next;
}
$e, $copy, '', $item->pub_note, 1) if $item->pub_note;
if($evt) {
- respond_with_status($conn, $total, $try_count, $in_count, $evt);
+ $$report_args{evt} = $evt;
+ respond_with_status($report_args);
next;
}
$e, $copy, '', $item->priv_note, 1) if $item->priv_note;
if($evt) {
- respond_with_status($conn, $total, $try_count, $in_count, $evt);
+ $$report_args{evt} = $evt;
+ respond_with_status($report_args);
next;
}
# Item import succeeded
# --------------------------------------------------------------------------------
$e->commit;
- respond_with_status($conn, $total, $try_count, ++$in_count, undef, imported_as => $copy->id);
+ $$report_args{in_count}++;
+ respond_with_status($report_args);
+ $logger->info("vl: successfully imported item " . $item->barcode);
}
+
}
+
$roe->rollback;
return undef;
}
sub respond_with_status {
- my($conn, $total, $try_count, $success_count, $err, %args) = @_;
- $conn->respond({
- total => $total,
- progress => $try_count,
- err_event => $err,
- success_count => $success_count, %args }) if $err or ($try_count % 5 == 0);
+ my $args = shift;
+ my $e = $$args{e};
+
+ # If the import failed, track the failure reason
+
+ my $error = $$args{import_error};
+ my $evt = $$args{evt};
+
+ if($error or $evt) {
+
+ my $item = $$args{import_item};
+ $logger->info("vl: unable to import item " . $item->barcode);
+
+ $error ||= 'general.unknown';
+ $item->import_error($error);
+
+ if($evt) {
+ my $detail = sprintf("%s : %s", $evt->{textcode}, substr($evt->{desc}, 0, 140));
+ $item->error_detail($detail);
+ }
+
+ # state of the editor is unknown at this point. Force a rollback and start over.
+ $e->rollback;
+ $e = new_editor(xact => 1);
+ $e->update_vandelay_import_item($item);
+ $e->commit;
+ }
+
+ if($$args{report_all} or ($$args{progress} % $$args{step}) == 0) {
+ $$args{conn}->respond({
+ map { $_ => $args->{$_} } qw/total progress success_count/,
+ err_event => $evt
+ });
+ $$args{step} *= 2 unless $$args{step} == 256;
+ }
+
+ $$args{progress}++;
}
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.match_set.get_tree",
+ method => "match_set_get_tree",
+ api_level => 1,
+ argc => 2,
+ signature => {
+ desc => q/For a given vms object, return a tree of match set points
+ represented by a vmsp object with recursively fleshed
+ children./
+ }
+);
+
+sub match_set_get_tree {
+ my ($self, $conn, $authtoken, $match_set_id) = @_;
+
+ $match_set_id = int($match_set_id) or return;
+
+ my $e = new_editor("authtoken" => $authtoken);
+ $e->checkauth or return $e->die_event;
+
+ my $set = $e->retrieve_vandelay_match_set($match_set_id) or
+ return $e->die_event;
+
+ $e->allowed("ADMIN_IMPORT_MATCH_SET", $set->owner) or
+ return $e->die_event;
+
+ my $tree = $e->search_vandelay_match_set_point([
+ {"match_set" => $match_set_id, "parent" => undef},
+ {"flesh" => -1, "flesh_fields" => {"vmsp" => ["children"]}}
+ ]) or return $e->die_event;
+
+ return pop @$tree;
+}
+
+
+__PACKAGE__->register_method(
+ api_name => "open-ils.vandelay.match_set.update",
+ method => "match_set_update_tree",
+ api_level => 1,
+ argc => 3,
+ signature => {
+ desc => q/Replace any vmsp objects associated with a given (by ID) vms
+ with the given objects (recursively fleshed vmsp tree)./
+ }
+);
+
+sub _walk_new_vmsp {
+ my ($e, $match_set_id, $node, $parent_id) = @_;
+
+ my $point = new Fieldmapper::vandelay::match_set_point;
+ $point->parent($parent_id);
+ $point->match_set($match_set_id);
+ $point->$_($node->$_) for (qw/bool_op svf tag subfield negate quality/);
+
+ $e->create_vandelay_match_set_point($point) or return $e->die_event;
+
+ $parent_id = $e->data->id;
+ if ($node->children && @{$node->children}) {
+ for (@{$node->children}) {
+ return $e->die_event if
+ _walk_new_vmsp($e, $match_set_id, $_, $parent_id);
+ }
+ }
+
+ return;
+}
+
+sub match_set_update_tree {
+ my ($self, $conn, $authtoken, $match_set_id, $tree) = @_;
+
+ my $e = new_editor("xact" => 1, "authtoken" => $authtoken);
+ $e->checkauth or return $e->die_event;
+
+ my $set = $e->retrieve_vandelay_match_set($match_set_id) or
+ return $e->die_event;
+
+ $e->allowed("ADMIN_IMPORT_MATCH_SET", $set->owner) or
+ return $e->die_event;
+
+ my $existing = $e->search_vandelay_match_set_point([
+ {"match_set" => $match_set_id},
+ {"order_by" => {"vmsp" => "id DESC"}}
+ ]) or return $e->die_event;
+
+ # delete points, working up from leaf points to the root
+ while(@$existing) {
+ for my $point (shift @$existing) {
+ if( grep {$_->parent eq $point->id} @$existing) {
+ push(@$existing, $point);
+ } else {
+ $e->delete_vandelay_match_set_point($point) or return $e->die_event;
+ }
+ }
+ }
+
+ _walk_new_vmsp($e, $match_set_id, $tree);
+
+ $e->commit or return $e->die_event;
+}
1;
SELECT evergreen.change_db_setting('search_path', ARRAY['evergreen','public','pg_catalog']);
+CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
+
CREATE OR REPLACE FUNCTION evergreen.lowercase( TEXT ) RETURNS TEXT AS $$
return lc(shift);
$$ LANGUAGE PLPERLU STRICT IMMUTABLE;
CREATE SCHEMA vandelay;
+CREATE TABLE vandelay.match_set (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL,
+ owner INT NOT NULL REFERENCES actor.org_unit (id) ON DELETE CASCADE,
+ mtype TEXT NOT NULL DEFAULT 'biblio', -- 'biblio','authority','mfhd'?, others?
+ CONSTRAINT name_once_per_owner_mtype UNIQUE (name, owner, mtype)
+);
+
+-- Table to define match points, either FF via SVF or tag+subfield
+CREATE TABLE vandelay.match_set_point (
+ id SERIAL PRIMARY KEY,
+ match_set INT REFERENCES vandelay.match_set (id),
+ parent INT REFERENCES vandelay.match_set_point (id),
+ bool_op TEXT CHECK (bool_op IS NULL OR (bool_op IN ('AND','OR','NOT'))),
+ svf TEXT REFERENCES config.record_attr_definition (name),
+ tag TEXT,
+ subfield TEXT,
+ negate BOOL DEFAULT FALSE,
+ quality INT NOT NULL DEFAULT 1, -- higher is better
+ CONSTRAINT vmsp_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
+ CONSTRAINT vmsp_need_a_tag_or_a_ff_or_a_bo CHECK (
+ (tag IS NOT NULL AND svf IS NULL AND bool_op IS NULL) OR
+ (tag IS NULL AND svf IS NOT NULL AND bool_op IS NULL) OR
+ (tag IS NULL AND svf IS NULL AND bool_op IS NOT NULL)
+ )
+);
+
+CREATE TABLE vandelay.match_set_quality (
+ id SERIAL PRIMARY KEY,
+ match_set INT NOT NULL REFERENCES vandelay.match_set (id),
+ svf TEXT REFERENCES config.record_attr_definition,
+ tag TEXT,
+ subfield TEXT,
+ value TEXT NOT NULL,
+ quality INT NOT NULL DEFAULT 1, -- higher is better
+ CONSTRAINT vmsq_need_a_subfield_with_a_tag CHECK ((tag IS NOT NULL AND subfield IS NOT NULL) OR tag IS NULL),
+ CONSTRAINT vmsq_need_a_tag_or_a_ff CHECK ((tag IS NOT NULL AND svf IS NULL) OR (tag IS NULL AND svf IS NOT NULL))
+);
+CREATE UNIQUE INDEX vmsq_def_once_per_set ON vandelay.match_set_quality (match_set, COALESCE(tag,''), COALESCE(subfield,''), COALESCE(svf,''), value);
+
+
CREATE TABLE vandelay.queue (
id BIGSERIAL PRIMARY KEY,
owner INT NOT NULL REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
name TEXT NOT NULL,
complete BOOL NOT NULL DEFAULT FALSE,
queue_type TEXT NOT NULL DEFAULT 'bib' CHECK (queue_type IN ('bib','authority')),
+ match_set INT REFERENCES vandelay.match_set (id) ON UPDATE CASCADE ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
CONSTRAINT vand_queue_name_once_per_owner_const UNIQUE (owner,name,queue_type)
);
create_time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
import_time TIMESTAMP WITH TIME ZONE,
purpose TEXT NOT NULL DEFAULT 'import' CHECK (purpose IN ('import','overlay')),
- marc TEXT NOT NULL
+ marc TEXT NOT NULL,
+ quality INT NOT NULL DEFAULT 0
);
code TEXT UNIQUE NOT NULL,
description TEXT,
xpath TEXT NOT NULL,
- remove TEXT NOT NULL DEFAULT '',
- ident BOOL NOT NULL DEFAULT FALSE
+ remove TEXT NOT NULL DEFAULT ''
);
-- Each TEXT field (other than 'name') should hold an XPath predicate for pulling the data needed
CONSTRAINT vand_import_item_attr_def_idx UNIQUE (owner,name)
);
+CREATE TABLE vandelay.import_error (
+ code TEXT PRIMARY KEY,
+ description TEXT NOT NULL -- i18n
+);
+
CREATE TABLE vandelay.bib_queue (
queue_type TEXT NOT NULL DEFAULT 'bib' CHECK (queue_type = 'bib'),
item_attr_def BIGINT REFERENCES vandelay.import_item_attr_definition (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
ALTER TABLE vandelay.bib_queue ADD PRIMARY KEY (id);
CREATE TABLE vandelay.queued_bib_record (
- queue INT NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
- bib_source INT REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED,
- imported_as BIGINT REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED
+ queue INT NOT NULL REFERENCES vandelay.bib_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ bib_source INT REFERENCES config.bib_source (id) DEFERRABLE INITIALLY DEFERRED,
+ imported_as BIGINT REFERENCES biblio.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
+ import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ error_detail TEXT
) INHERITS (vandelay.queued_record);
ALTER TABLE vandelay.queued_bib_record ADD PRIMARY KEY (id);
CREATE INDEX queued_bib_record_queue_idx ON vandelay.queued_bib_record (queue);
CREATE TABLE vandelay.bib_match (
id BIGSERIAL PRIMARY KEY,
- field_type TEXT NOT NULL CHECK (field_type in ('isbn','tcn_value','id')),
- matched_attr INT REFERENCES vandelay.queued_bib_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
queued_record BIGINT REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
- eg_record BIGINT REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
+ eg_record BIGINT REFERENCES biblio.record_entry (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ quality INT NOT NULL DEFAULT 1,
+ match_score INT NOT NULL DEFAULT 0
);
--- DROP TABLE vandelay.import_item CASCADE;
CREATE TABLE vandelay.import_item (
id BIGSERIAL PRIMARY KEY,
record BIGINT NOT NULL REFERENCES vandelay.queued_bib_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
definition BIGINT NOT NULL REFERENCES vandelay.import_item_attr_definition (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ error_detail TEXT,
owning_lib INT,
circ_lib INT,
call_number TEXT,
replace_spec TEXT,
strip_spec TEXT,
preserve_spec TEXT,
+ lwm_ratio NUMERIC,
CONSTRAINT vand_merge_prof_owner_name_idx UNIQUE (owner,name),
CONSTRAINT add_replace_strip_or_preserve CHECK ((preserve_spec IS NOT NULL OR replace_spec IS NOT NULL) OR (preserve_spec IS NULL AND replace_spec IS NULL))
);
+CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
+DECLARE
+ ldr TEXT;
+ tval TEXT;
+ tval_rec RECORD;
+ bval TEXT;
+ bval_rec RECORD;
+ retval config.marc21_rec_type_map%ROWTYPE;
+BEGIN
+ ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
+
+ IF ldr IS NULL OR ldr = '' THEN
+ SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
+ RETURN retval;
+ END IF;
+
+ SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
+ SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
+
+
+ tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
+ bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
+
+ -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
+
+ SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
+
+
+ IF retval.code IS NULL THEN
+ SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
+ END IF;
+
+ RETURN retval;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
+DECLARE
+ rtype TEXT;
+ ff_pos RECORD;
+ tag_data RECORD;
+ val TEXT;
+BEGIN
+ rtype := (vandelay.marc21_record_type( marc )).code;
+ FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
+ IF ff_pos.tag = 'ldr' THEN
+ val := oils_xpath_string('//*[local-name()="leader"]', marc);
+ IF val IS NOT NULL THEN
+ val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
+ RETURN val;
+ END IF;
+ ELSE
+ FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
+ val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
+ RETURN val;
+ END LOOP;
+ END IF;
+ val := REPEAT( ff_pos.default_val, ff_pos.length );
+ RETURN val;
+ END LOOP;
+
+ RETURN NULL;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE TYPE biblio.record_ff_map AS (record BIGINT, ff_name TEXT, ff_value TEXT);
+CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
+DECLARE
+ tag_data TEXT;
+ rtype TEXT;
+ ff_pos RECORD;
+ output biblio.record_ff_map%ROWTYPE;
+BEGIN
+ rtype := (vandelay.marc21_record_type( marc )).code;
+
+ FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
+ output.ff_name := ff_pos.fixed_field;
+ output.ff_value := NULL;
+
+ IF ff_pos.tag = 'ldr' THEN
+ output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
+ IF output.ff_value IS NOT NULL THEN
+ output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
+ RETURN NEXT output;
+ output.ff_value := NULL;
+ END IF;
+ ELSE
+ FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
+ output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
+ IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
+ RETURN NEXT output;
+ output.ff_value := NULL;
+ END LOOP;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
+CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
+DECLARE
+ rowid INT := 0;
+ _007 TEXT;
+ ptype config.marc21_physical_characteristic_type_map%ROWTYPE;
+ psf config.marc21_physical_characteristic_subfield_map%ROWTYPE;
+ pval config.marc21_physical_characteristic_value_map%ROWTYPE;
+ retval biblio.marc21_physical_characteristics%ROWTYPE;
+BEGIN
+
+ _007 := oils_xpath_string( '//*[@tag="007"]', marc );
+
+ IF _007 IS NOT NULL AND _007 <> '' THEN
+ SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
+
+ IF ptype.ptype_key IS NOT NULL THEN
+ FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
+ SELECT * INTO pval FROM config.marc21_physical_characteristic_value_map WHERE ptype_subfield = psf.id AND value = SUBSTRING( _007, psf.start_pos + 1, psf.length );
+
+ IF pval.id IS NOT NULL THEN
+ rowid := rowid + 1;
+ retval.id := rowid;
+ retval.ptype := ptype.ptype_key;
+ retval.subfield := psf.id;
+ retval.value := pval.id;
+ RETURN NEXT retval;
+ END IF;
+
+ END LOOP;
+ END IF;
+ END IF;
+
+ RETURN;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE TYPE vandelay.flat_marc AS ( tag CHAR(3), ind1 TEXT, ind2 TEXT, subfield TEXT, value TEXT );
+CREATE OR REPLACE FUNCTION vandelay.flay_marc ( TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
+
+use MARC::Record;
+use MARC::File::XML (BinaryEncoding => 'UTF-8');
+use MARC::Charset;
+use strict;
+
+MARC::Charset->assume_unicode(1);
+
+my $xml = shift;
+my $r = MARC::Record->new_from_xml( $xml );
+
+return_next( { tag => 'LDR', value => $r->leader } );
+
+for my $f ( $r->fields ) {
+ if ($f->is_control_field) {
+ return_next({ tag => $f->tag, value => $f->data });
+ } else {
+ for my $s ($f->subfields) {
+ return_next({
+ tag => $f->tag,
+ ind1 => $f->indicator(1),
+ ind2 => $f->indicator(2),
+ subfield => $s->[0],
+ value => $s->[1]
+ });
+
+ if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
+ my $trim = $f->indicator(2) || 0;
+ return_next({
+ tag => 'tnf',
+ ind1 => $f->indicator(1),
+ ind2 => $f->indicator(2),
+ subfield => 'a',
+ value => substr( $s->[1], $trim )
+ });
+ }
+ }
+ }
+}
+
+return undef;
+
+$func$ LANGUAGE PLPERLU;
+
+CREATE OR REPLACE FUNCTION vandelay.flatten_marc ( marc TEXT ) RETURNS SETOF vandelay.flat_marc AS $func$
+DECLARE
+ output vandelay.flat_marc%ROWTYPE;
+ field RECORD;
+BEGIN
+ FOR field IN SELECT * FROM vandelay.flay_marc( marc ) LOOP
+ output.ind1 := field.ind1;
+ output.ind2 := field.ind2;
+ output.tag := field.tag;
+ output.subfield := field.subfield;
+ IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
+ output.value := naco_normalize(field.value, field.subfield);
+ ELSE
+ output.value := field.value;
+ END IF;
+
+ CONTINUE WHEN output.value IS NULL;
+
+ RETURN NEXT output;
+ END LOOP;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT, attr_defs TEXT[]) RETURNS hstore AS $_$
+DECLARE
+ transformed_xml TEXT;
+ prev_xfrm TEXT;
+ normalizer RECORD;
+ xfrm config.xml_transform%ROWTYPE;
+ attr_value TEXT;
+ new_attrs HSTORE := ''::HSTORE;
+ attr_def config.record_attr_definition%ROWTYPE;
+BEGIN
+
+ FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE name IN (SELECT * FROM UNNEST(attr_defs)) ORDER BY format LOOP
+
+ IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
+ SELECT ARRAY_TO_STRING(ARRAY_ACCUM(x.value), COALESCE(attr_def.joiner,' ')) INTO attr_value
+ FROM vandelay.flatten_marc(xml) AS x
+ WHERE x.tag LIKE attr_def.tag
+ AND CASE
+ WHEN attr_def.sf_list IS NOT NULL
+ THEN POSITION(x.subfield IN attr_def.sf_list) > 0
+ ELSE TRUE
+ END
+ GROUP BY x.tag
+ ORDER BY x.tag
+ LIMIT 1;
+
+ ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
+ attr_value := vandelay.marc21_extract_fixed_field(xml, attr_def.fixed_field);
+
+ ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
+
+ SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
+
+ -- See if we can skip the XSLT ... it's expensive
+ IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
+ -- Can't skip the transform
+ IF xfrm.xslt <> '---' THEN
+ transformed_xml := oils_xslt_process(xml,xfrm.xslt);
+ ELSE
+ transformed_xml := xml;
+ END IF;
+
+ prev_xfrm := xfrm.name;
+ END IF;
+
+ IF xfrm.name IS NULL THEN
+ -- just grab the marcxml (empty) transform
+ SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
+ prev_xfrm := xfrm.name;
+ END IF;
+
+ attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
+
+ ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
+ SELECT value::TEXT INTO attr_value
+ FROM vandelay.marc21_physical_characteristics(xml)
+ WHERE subfield = attr_def.phys_char_sf
+ LIMIT 1; -- Just in case ...
+
+ END IF;
+
+ -- apply index normalizers to attr_value
+ FOR normalizer IN
+ SELECT n.func AS func,
+ n.param_count AS param_count,
+ m.params AS params
+ FROM config.index_normalizer n
+ JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
+ WHERE attr = attr_def.name
+ ORDER BY m.pos LOOP
+ EXECUTE 'SELECT ' || normalizer.func || '(' ||
+ quote_literal( attr_value ) ||
+ CASE
+ WHEN normalizer.param_count > 0
+ THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
+ ELSE ''
+ END ||
+ ')' INTO attr_value;
+
+ END LOOP;
+
+ -- Add the new value to the hstore
+ new_attrs := new_attrs || hstore( attr_def.name, attr_value );
+
+ END LOOP;
+
+ RETURN new_attrs;
+END;
+$_$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.extract_rec_attrs ( xml TEXT ) RETURNS hstore AS $_$
+ SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_ACCUM(name) FROM config.record_attr_definition));
+$_$ LANGUAGE SQL;
+
+-- Everything between this comment and the beginning of the definition of
+-- vandelay.match_bib_record() is strictly in service of that function.
+CREATE TYPE vandelay.match_set_test_result AS (record BIGINT, quality INTEGER);
+
+CREATE OR REPLACE FUNCTION vandelay.match_set_test_marcxml(
+ match_set_id INTEGER, record_xml TEXT
+) RETURNS SETOF vandelay.match_set_test_result AS $$
+DECLARE
+ tags_rstore HSTORE;
+ svf_rstore HSTORE;
+ coal TEXT;
+ joins TEXT;
+ query_ TEXT;
+ wq TEXT;
+ qvalue INTEGER;
+ rec RECORD;
+BEGIN
+ tags_rstore := vandelay.flatten_marc_hstore(record_xml);
+ svf_rstore := vandelay.extract_rec_attrs(record_xml);
+
+ CREATE TEMPORARY TABLE _vandelay_tmp_qrows (q INTEGER);
+ CREATE TEMPORARY TABLE _vandelay_tmp_jrows (j TEXT);
+
+ -- generate the where clause and return that directly (into wq), and as
+ -- a side-effect, populate the _vandelay_tmp_[qj]rows tables.
+ wq := vandelay.get_expr_from_match_set(match_set_id);
+
+ query_ := 'SELECT bre.id AS record, ';
+
+ -- qrows table is for the quality bits we add to the SELECT clause
+ SELECT ARRAY_TO_STRING(
+ ARRAY_ACCUM('COALESCE(n' || q::TEXT || '.quality, 0)'), ' + '
+ ) INTO coal FROM _vandelay_tmp_qrows;
+
+ -- our query string so far is the SELECT clause and the inital FROM.
+ -- no JOINs yet nor the WHERE clause
+ query_ := query_ || coal || ' AS quality ' || E'\n' ||
+ 'FROM biblio.record_entry bre ';
+
+ -- jrows table is for the joins we must make (and the real text conditions)
+ SELECT ARRAY_TO_STRING(ARRAY_ACCUM(j), E'\n') INTO joins
+ FROM _vandelay_tmp_jrows;
+
+ -- add those joins and the where clause to our query.
+ query_ := query_ || joins || E'\n' || 'WHERE ' || wq || ' AND not bre.deleted';
+
+ -- this will return rows of record,quality
+ FOR rec IN EXECUTE query_ USING tags_rstore, svf_rstore LOOP
+ RETURN NEXT rec;
+ END LOOP;
+
+ DROP TABLE _vandelay_tmp_qrows;
+ DROP TABLE _vandelay_tmp_jrows;
+ RETURN;
+END;
+
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.flatten_marc_hstore(
+ record_xml TEXT
+) RETURNS HSTORE AS $$
+BEGIN
+ RETURN (SELECT
+ HSTORE(
+ ARRAY_ACCUM(tag || (COALESCE(subfield, ''))),
+ ARRAY_ACCUM(value)
+ )
+ FROM (
+ SELECT tag, subfield, ARRAY_ACCUM(value)::TEXT AS value
+ FROM vandelay.flatten_marc(record_xml)
+ GROUP BY tag, subfield ORDER BY tag, subfield
+ ) subquery
+ );
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set(
+ match_set_id INTEGER
+) RETURNS TEXT AS $$
+DECLARE
+ root vandelay.match_set_point;
+BEGIN
+ SELECT * INTO root FROM vandelay.match_set_point
+ WHERE parent IS NULL AND match_set = match_set_id;
+
+ RETURN vandelay.get_expr_from_match_set_point(root);
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.get_expr_from_match_set_point(
+ node vandelay.match_set_point
+) RETURNS TEXT AS $$
+DECLARE
+ q TEXT;
+ i INTEGER;
+ this_op TEXT;
+ children INTEGER[];
+ child vandelay.match_set_point;
+BEGIN
+ SELECT ARRAY_ACCUM(id) INTO children FROM vandelay.match_set_point
+ WHERE parent = node.id;
+
+ IF ARRAY_LENGTH(children, 1) > 0 THEN
+ this_op := vandelay._get_expr_render_one(node);
+ q := '(';
+ i := 1;
+ WHILE children[i] IS NOT NULL LOOP
+ SELECT * INTO child FROM vandelay.match_set_point
+ WHERE id = children[i];
+ IF i > 1 THEN
+ q := q || ' ' || this_op || ' ';
+ END IF;
+ i := i + 1;
+ q := q || vandelay.get_expr_from_match_set_point(child);
+ END LOOP;
+ q := q || ')';
+ RETURN q;
+ ELSIF node.bool_op IS NULL THEN
+ PERFORM vandelay._get_expr_push_qrow(node);
+ PERFORM vandelay._get_expr_push_jrow(node);
+ RETURN vandelay._get_expr_render_one(node);
+ ELSE
+ RETURN '';
+ END IF;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay._get_expr_push_qrow(
+ node vandelay.match_set_point
+) RETURNS VOID AS $$
+DECLARE
+BEGIN
+ INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay._get_expr_push_jrow(
+ node vandelay.match_set_point
+) RETURNS VOID AS $$
+DECLARE
+ jrow TEXT;
+ my_alias TEXT;
+ op TEXT;
+ tagkey TEXT;
+BEGIN
+ IF node.negate THEN
+ op := '<>';
+ ELSE
+ op := '=';
+ END IF;
+
+ IF node.tag IS NOT NULL THEN
+ tagkey := node.tag;
+ IF node.subfield IS NOT NULL THEN
+ tagkey := tagkey || node.subfield;
+ END IF;
+ END IF;
+
+ my_alias := 'n' || node.id::TEXT;
+
+ jrow := 'LEFT JOIN (SELECT *, ' || node.quality ||
+ ' AS quality FROM metabib.';
+ IF node.tag IS NOT NULL THEN
+ jrow := jrow || 'full_rec) ' || my_alias || ' ON (' ||
+ my_alias || '.record = bre.id AND ' || my_alias || '.tag = ''' ||
+ node.tag || '''';
+ IF node.subfield IS NOT NULL THEN
+ jrow := jrow || ' AND ' || my_alias || '.subfield = ''' ||
+ node.subfield || '''';
+ END IF;
+ jrow := jrow || ' AND (' || my_alias || '.value ' || op ||
+ ' ANY(($1->''' || tagkey || ''')::TEXT[])))';
+ ELSE -- svf
+ jrow := jrow || 'record_attr) ' || my_alias || ' ON (' ||
+ my_alias || '.id = bre.id AND (' ||
+ my_alias || '.attrs->''' || node.svf ||
+ ''' ' || op || ' $2->''' || node.svf || '''))';
+ END IF;
+ INSERT INTO _vandelay_tmp_jrows (j) VALUES (jrow);
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay._get_expr_render_one(
+ node vandelay.match_set_point
+) RETURNS TEXT AS $$
+DECLARE
+ s TEXT;
+BEGIN
+ IF node.bool_op IS NOT NULL THEN
+ RETURN node.bool_op;
+ ELSE
+ RETURN '(n' || node.id::TEXT || '.id IS NOT NULL)';
+ END IF;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.match_bib_record() RETURNS TRIGGER AS $func$
+DECLARE
+ incoming_existing_id TEXT;
+ my_bib_queue vandelay.bib_queue%ROWTYPE;
+ test_result vandelay.match_set_test_result%ROWTYPE;
+ tmp_rec BIGINT;
+BEGIN
+ IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+ RETURN NEW;
+ END IF;
+
+ DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
+
+ SELECT * INTO my_bib_queue FROM vandelay.bib_queue WHERE id = NEW.queue;
+
+ IF my_bib_queue.match_set IS NULL THEN
+ RETURN NEW;
+ END IF;
+
+ NEW.quality := vandelay.measure_record_quality( NEW.marc, my_bib_queue.match_set );
+
+ -- Perfect matches on 901$c exit early with a match with high quality.
+ incoming_existing_id :=
+ oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]', NEW.marc);
+
+ IF incoming_existing_id IS NOT NULL AND incoming_existing_id != '' THEN
+ SELECT id INTO tmp_rec FROM biblio.record_entry WHERE id = incoming_existing_id::bigint;
+ IF tmp_rec IS NOT NULL THEN
+ INSERT INTO vandelay.bib_match (queued_record, eg_record, quality) VALUES ( NEW.id, incoming_existing_id::bigint, 9999);
+ RETURN NEW;
+ END IF;
+ END IF;
+
+
+ FOR test_result IN SELECT * FROM
+ vandelay.match_set_test_marcxml(my_bib_queue.match_set, NEW.marc) LOOP
+
+ INSERT INTO vandelay.bib_match ( queued_record, eg_record, match_score, quality )
+ SELECT NEW.id,
+ test_result.record,
+ test_result.quality,
+ vandelay.measure_record_quality( b.marc, my_bib_queue.match_set )
+ FROM biblio.record_entry b
+ WHERE id = test_result.record;
+
+ END LOOP;
+
+ RETURN NEW;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.measure_record_quality ( xml TEXT, match_set_id INT ) RETURNS INT AS $_$
+DECLARE
+ out_q INT := 0;
+ rvalue TEXT;
+ test vandelay.match_set_quality%ROWTYPE;
+BEGIN
+
+ FOR test IN SELECT * FROM vandelay.match_set_quality WHERE match_set = match_set_id LOOP
+ IF test.tag IS NOT NULL THEN
+ FOR rvalue IN SELECT value FROM vandelay.flatten_marc( xml ) WHERE tag = test.tag AND subfield = test.subfield LOOP
+ IF test.value = rvalue THEN
+ out_q := out_q + test.quality;
+ END IF;
+ END LOOP;
+ ELSE
+ IF test.value = vandelay.extract_rec_attrs(xml, ARRAY[test.svf]) -> test.svf THEN
+ out_q := out_q + test.quality;
+ END IF;
+ END IF;
+ END LOOP;
+
+ RETURN out_q;
+END;
+$_$ LANGUAGE PLPGSQL;
CREATE TYPE vandelay.tcn_data AS (tcn TEXT, tcn_source TEXT, used BOOL);
CREATE OR REPLACE FUNCTION vandelay.find_bib_tcn_data ( xml TEXT ) RETURNS SETOF vandelay.tcn_data AS $_$
eg_marc TEXT;
v_marc TEXT;
replace_rule TEXT;
- match_count INT;
BEGIN
SELECT q.marc INTO v_marc
END;
$$ LANGUAGE PLPGSQL;
+CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record_with_best ( import_id BIGINT, merge_profile_id INT, lwm_ratio_value_p NUMERIC ) RETURNS BOOL AS $$
+DECLARE
+ eg_id BIGINT;
+ lwm_ratio_value NUMERIC;
+BEGIN
+
+ lwm_ratio_value := COALESCE(lwm_ratio_value_p, 0.0);
+
+ PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
+
+ IF FOUND THEN
+ -- RAISE NOTICE 'already imported, cannot auto-overlay'
+ RETURN FALSE;
+ END IF;
+
+ SELECT m.eg_record INTO eg_id
+ FROM vandelay.bib_match m
+ JOIN vandelay.queued_bib_record qr ON (m.queued_record = qr.id)
+ JOIN vandelay.bib_queue q ON (qr.queue = q.id)
+ JOIN biblio.record_entry r ON (r.id = m.eg_record)
+ WHERE m.queued_record = import_id
+ AND qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC >= lwm_ratio_value
+ ORDER BY m.match_score DESC, -- required match score
+ qr.quality::NUMERIC / COALESCE(NULLIF(m.quality,0),1)::NUMERIC DESC, -- quality tie breaker
+ m.id -- when in doubt, use the first match
+ LIMIT 1;
+
+ IF eg_id IS NULL THEN
+ -- RAISE NOTICE 'incoming record is not of high enough quality';
+ RETURN FALSE;
+ END IF;
+
+ RETURN vandelay.overlay_bib_record( import_id, eg_id, merge_profile_id );
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record_with_best ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
+ SELECT vandelay.auto_overlay_bib_record_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
+$$ LANGUAGE SQL;
+
CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_record ( import_id BIGINT, merge_profile_id INT ) RETURNS BOOL AS $$
DECLARE
eg_id BIGINT;
match_count INT;
- match_attr vandelay.bib_attr_definition%ROWTYPE;
BEGIN
PERFORM * FROM vandelay.queued_bib_record WHERE import_time IS NOT NULL AND id = import_id;
RETURN FALSE;
END IF;
- SELECT d.* INTO match_attr
- FROM vandelay.bib_attr_definition d
- JOIN vandelay.queued_bib_record_attr a ON (a.field = d.id)
- JOIN vandelay.bib_match m ON (m.matched_attr = a.id)
- WHERE m.queued_record = import_id;
-
- IF NOT (match_attr.xpath ~ '@tag="901"' AND match_attr.xpath ~ '@code="c"') THEN
- -- RAISE NOTICE 'not a 901c match: %', match_attr.xpath;
- RETURN FALSE;
- END IF;
-
+ -- Check that the one match is on the first 901c
SELECT m.eg_record INTO eg_id
- FROM vandelay.bib_match m
- WHERE m.queued_record = import_id
- LIMIT 1;
+ FROM vandelay.queued_bib_record q
+ JOIN vandelay.bib_match m ON (m.queued_record = q.id)
+ WHERE q.id = import_id
+ AND m.eg_record = oils_xpath_string('//*[@tag="901"]/*[@code="c"][1]',marc)::BIGINT;
- IF eg_id IS NULL THEN
+ IF NOT FOUND THEN
+ -- RAISE NOTICE 'not a 901c match';
RETURN FALSE;
END IF;
END;
$$ LANGUAGE PLPGSQL;
+CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( queue_id BIGINT, merge_profile_id INT, lwm_ratio_value NUMERIC ) RETURNS SETOF BIGINT AS $$
+DECLARE
+ queued_record vandelay.queued_bib_record%ROWTYPE;
+BEGIN
+
+ FOR queued_record IN SELECT * FROM vandelay.queued_bib_record WHERE queue = queue_id AND import_time IS NULL LOOP
+
+ IF vandelay.auto_overlay_bib_record_with_best( queued_record.id, merge_profile_id, lwm_ratio_value ) THEN
+ RETURN NEXT queued_record.id;
+ END IF;
+
+ END LOOP;
+
+ RETURN;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue_with_best ( import_id BIGINT, merge_profile_id INT ) RETURNS SETOF BIGINT AS $$
+ SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;
+$$ LANGUAGE SQL;
+
CREATE OR REPLACE FUNCTION vandelay.auto_overlay_bib_queue ( queue_id BIGINT ) RETURNS SETOF BIGINT AS $$
SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );
$$ LANGUAGE SQL;
atype TEXT;
adef RECORD;
BEGIN
+ IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+ RETURN NEW;
+ END IF;
+
FOR adef IN SELECT * FROM vandelay.bib_attr_definition LOOP
SELECT extract_marc_field('vandelay.queued_bib_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_bib_record WHERE id = NEW.id;
item_data vandelay.import_item%ROWTYPE;
BEGIN
+ IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+ RETURN NEW;
+ END IF;
+
SELECT item_attr_def INTO attr_def FROM vandelay.bib_queue WHERE id = NEW.queue;
FOR item_data IN SELECT * FROM vandelay.ingest_items( NEW.id::BIGINT, attr_def ) LOOP
END;
$func$ LANGUAGE PLPGSQL;
-CREATE OR REPLACE FUNCTION vandelay.match_bib_record ( ) RETURNS TRIGGER AS $func$
-DECLARE
- attr RECORD;
- attr_def RECORD;
- eg_rec RECORD;
- id_value TEXT;
- exact_id BIGINT;
+CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
BEGIN
-
- DELETE FROM vandelay.bib_match WHERE queued_record = NEW.id;
-
- SELECT * INTO attr_def FROM vandelay.bib_attr_definition WHERE xpath = '//*[@tag="901"]/*[@code="c"]' ORDER BY id LIMIT 1;
-
- IF attr_def IS NOT NULL AND attr_def.id IS NOT NULL THEN
- id_value := extract_marc_field('vandelay.queued_bib_record', NEW.id, attr_def.xpath, attr_def.remove);
-
- IF id_value IS NOT NULL AND id_value <> '' AND id_value ~ $r$^\d+$$r$ THEN
- SELECT id INTO exact_id FROM biblio.record_entry WHERE id = id_value::BIGINT AND NOT deleted;
- SELECT * INTO attr FROM vandelay.queued_bib_record_attr WHERE record = NEW.id and field = attr_def.id LIMIT 1;
- IF exact_id IS NOT NULL THEN
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, exact_id);
- END IF;
- END IF;
- END IF;
-
- IF exact_id IS NULL THEN
- FOR attr IN SELECT a.* FROM vandelay.queued_bib_record_attr a JOIN vandelay.bib_attr_definition d ON (d.id = a.field) WHERE record = NEW.id AND d.ident IS TRUE LOOP
-
- -- All numbers? check for an id match
- IF (attr.attr_value ~ $r$^\d+$$r$) THEN
- FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE id = attr.attr_value::BIGINT AND deleted IS FALSE LOOP
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
- END LOOP;
- END IF;
-
- -- Looks like an ISBN? check for an isbn match
- IF (attr.attr_value ~* $r$^[0-9x]+$$r$ AND character_length(attr.attr_value) IN (10,13)) THEN
- FOR eg_rec IN EXECUTE $$SELECT * FROM metabib.full_rec fr WHERE fr.value LIKE evergreen.lowercase('$$ || attr.attr_value || $$%') AND fr.tag = '020' AND fr.subfield = 'a'$$ LOOP
- PERFORM id FROM biblio.record_entry WHERE id = eg_rec.record AND deleted IS FALSE;
- IF FOUND THEN
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('isbn', attr.id, NEW.id, eg_rec.record);
- END IF;
- END LOOP;
-
- -- subcheck for isbn-as-tcn
- FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = 'i' || attr.attr_value AND deleted IS FALSE LOOP
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
- END LOOP;
- END IF;
-
- -- check for an OCLC tcn_value match
- IF (attr.attr_value ~ $r$^o\d+$$r$) THEN
- FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = regexp_replace(attr.attr_value,'^o','ocm') AND deleted IS FALSE LOOP
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
- END LOOP;
- END IF;
-
- -- check for a direct tcn_value match
- FOR eg_rec IN SELECT * FROM biblio.record_entry WHERE tcn_value = attr.attr_value AND deleted IS FALSE LOOP
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('tcn_value', attr.id, NEW.id, eg_rec.id);
- END LOOP;
-
- -- check for a direct item barcode match
- FOR eg_rec IN
- SELECT DISTINCT b.*
- FROM biblio.record_entry b
- JOIN asset.call_number cn ON (cn.record = b.id)
- JOIN asset.copy cp ON (cp.call_number = cn.id)
- WHERE cp.barcode = attr.attr_value AND cp.deleted IS FALSE
- LOOP
- INSERT INTO vandelay.bib_match (field_type, matched_attr, queued_record, eg_record) VALUES ('id', attr.id, NEW.id, eg_rec.id);
- END LOOP;
-
- END LOOP;
+ IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+ RETURN NEW;
END IF;
- RETURN NULL;
-END;
-$func$ LANGUAGE PLPGSQL;
-
-CREATE OR REPLACE FUNCTION vandelay.cleanup_bib_marc ( ) RETURNS TRIGGER AS $$
-BEGIN
DELETE FROM vandelay.queued_bib_record_attr WHERE record = OLD.id;
DELETE FROM vandelay.import_item WHERE record = OLD.id;
FOR EACH ROW EXECUTE PROCEDURE vandelay.ingest_bib_items();
CREATE TRIGGER zz_match_bibs_trigger
- AFTER INSERT OR UPDATE ON vandelay.queued_bib_record
+ BEFORE INSERT OR UPDATE ON vandelay.queued_bib_record
FOR EACH ROW EXECUTE PROCEDURE vandelay.match_bib_record();
code TEXT UNIQUE NOT NULL,
description TEXT,
xpath TEXT NOT NULL,
- remove TEXT NOT NULL DEFAULT '',
- ident BOOL NOT NULL DEFAULT FALSE
+ remove TEXT NOT NULL DEFAULT ''
);
CREATE TABLE vandelay.authority_queue (
CREATE TABLE vandelay.queued_authority_record (
queue INT NOT NULL REFERENCES vandelay.authority_queue (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
- imported_as INT REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
+ imported_as INT REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
+ import_error TEXT REFERENCES vandelay.import_error (code) ON DELETE SET NULL ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
+ error_detail TEXT
) INHERITS (vandelay.queued_record);
ALTER TABLE vandelay.queued_authority_record ADD PRIMARY KEY (id);
CREATE INDEX queued_authority_record_queue_idx ON vandelay.queued_authority_record (queue);
CREATE TABLE vandelay.authority_match (
id BIGSERIAL PRIMARY KEY,
- matched_attr INT REFERENCES vandelay.queued_authority_record_attr (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
queued_record BIGINT REFERENCES vandelay.queued_authority_record (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
- eg_record BIGINT REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED
+ eg_record BIGINT REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
+ quality INT NOT NULL DEFAULT 0
);
CREATE OR REPLACE FUNCTION vandelay.ingest_authority_marc ( ) RETURNS TRIGGER AS $$
atype TEXT;
adef RECORD;
BEGIN
+ IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+ RETURN NEW;
+ END IF;
+
FOR adef IN SELECT * FROM vandelay.authority_attr_definition LOOP
SELECT extract_marc_field('vandelay.queued_authority_record', id, adef.xpath, adef.remove) INTO value FROM vandelay.queued_authority_record WHERE id = NEW.id;
CREATE OR REPLACE FUNCTION vandelay.cleanup_authority_marc ( ) RETURNS TRIGGER AS $$
BEGIN
+ IF TG_OP IN ('INSERT','UPDATE') AND NEW.imported_as IS NOT NULL THEN
+ RETURN NEW;
+ END IF;
+
DELETE FROM vandelay.queued_authority_record_attr WHERE record = OLD.id;
IF TG_OP = 'UPDATE' THEN
RETURN NEW;
SELECT * FROM biblio.extract_metabib_field_entry($1, ' ');
$func$ LANGUAGE SQL;
+CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
+DECLARE
+ auth authority.record_entry%ROWTYPE;
+ output authority.full_rec%ROWTYPE;
+ field RECORD;
+BEGIN
+ SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
+
+ FOR field IN SELECT * FROM vandelay.flatten_marc( auth.marc ) LOOP
+ output.record := rid;
+ output.ind1 := field.ind1;
+ output.ind2 := field.ind2;
+ output.tag := field.tag;
+ output.subfield := field.subfield;
+ output.value := field.value;
+
+ RETURN NEXT output;
+ END LOOP;
+END;
+$func$ LANGUAGE PLPGSQL;
+
CREATE OR REPLACE FUNCTION biblio.flatten_marc ( rid BIGINT ) RETURNS SETOF metabib.full_rec AS $func$
DECLARE
bib biblio.record_entry%ROWTYPE;
BEGIN
SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
- FOR field IN SELECT * FROM biblio.flatten_marc( bib.marc ) LOOP
+ FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
output.record := rid;
output.ind1 := field.ind1;
output.ind2 := field.ind2;
output.tag := field.tag;
output.subfield := field.subfield;
- IF field.subfield IS NOT NULL AND field.tag NOT IN ('020','022','024') THEN -- exclude standard numbers and control fields
- output.value := naco_normalize(field.value, field.subfield);
- ELSE
- output.value := field.value;
- END IF;
-
- CONTINUE WHEN output.value IS NULL;
+ output.value := field.value;
RETURN NEXT output;
END LOOP;
-- )x(record int, tag text, ind1 text, ind2 text, subfield text, value text);
-- $func$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION biblio.flatten_marc ( TEXT ) RETURNS SETOF metabib.full_rec AS $func$
-
-use MARC::Record;
-use MARC::File::XML (BinaryEncoding => 'UTF-8');
-use MARC::Charset;
-
-MARC::Charset->assume_unicode(1);
-
-my $xml = shift;
-my $r = MARC::Record->new_from_xml( $xml );
-
-return_next( { tag => 'LDR', value => $r->leader } );
-
-for my $f ( $r->fields ) {
- if ($f->is_control_field) {
- return_next({ tag => $f->tag, value => $f->data });
- } else {
- for my $s ($f->subfields) {
- return_next({
- tag => $f->tag,
- ind1 => $f->indicator(1),
- ind2 => $f->indicator(2),
- subfield => $s->[0],
- value => $s->[1]
- });
-
- if ( $f->tag eq '245' and $s->[0] eq 'a' ) {
- my $trim = $f->indicator(2) || 0;
- return_next({
- tag => 'tnf',
- ind1 => $f->indicator(1),
- ind2 => $f->indicator(2),
- subfield => 'a',
- value => substr( $s->[1], $trim )
- });
- }
- }
- }
-}
-
-return undef;
-
-$func$ LANGUAGE PLPERLU;
-
-CREATE OR REPLACE FUNCTION vandelay.marc21_record_type( marc TEXT ) RETURNS config.marc21_rec_type_map AS $func$
-DECLARE
- ldr TEXT;
- tval TEXT;
- tval_rec RECORD;
- bval TEXT;
- bval_rec RECORD;
- retval config.marc21_rec_type_map%ROWTYPE;
-BEGIN
- ldr := oils_xpath_string( '//*[local-name()="leader"]', marc );
-
- IF ldr IS NULL OR ldr = '' THEN
- SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
- RETURN retval;
- END IF;
-
- SELECT * INTO tval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'Type' LIMIT 1; -- They're all the same
- SELECT * INTO bval_rec FROM config.marc21_ff_pos_map WHERE fixed_field = 'BLvl' LIMIT 1; -- They're all the same
-
-
- tval := SUBSTRING( ldr, tval_rec.start_pos + 1, tval_rec.length );
- bval := SUBSTRING( ldr, bval_rec.start_pos + 1, bval_rec.length );
-
- -- RAISE NOTICE 'type %, blvl %, ldr %', tval, bval, ldr;
-
- SELECT * INTO retval FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';
-
-
- IF retval.code IS NULL THEN
- SELECT * INTO retval FROM config.marc21_rec_type_map WHERE code = 'BKS';
- END IF;
-
- RETURN retval;
-END;
-$func$ LANGUAGE PLPGSQL;
CREATE OR REPLACE FUNCTION biblio.marc21_record_type( rid BIGINT ) RETURNS config.marc21_rec_type_map AS $func$
SELECT * FROM vandelay.marc21_record_type( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
$func$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION vandelay.marc21_extract_fixed_field( marc TEXT, ff TEXT ) RETURNS TEXT AS $func$
-DECLARE
- rtype TEXT;
- ff_pos RECORD;
- tag_data RECORD;
- val TEXT;
-BEGIN
- rtype := (vandelay.marc21_record_type( marc )).code;
- FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = ff AND rec_type = rtype ORDER BY tag DESC LOOP
- IF ff_pos.tag = 'ldr' THEN
- val := oils_xpath_string('//*[local-name()="leader"]', marc);
- IF val IS NOT NULL THEN
- val := SUBSTRING( val, ff_pos.start_pos + 1, ff_pos.length );
- RETURN val;
- END IF;
- ELSE
- FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
- val := SUBSTRING( tag_data.value, ff_pos.start_pos + 1, ff_pos.length );
- RETURN val;
- END LOOP;
- END IF;
- val := REPEAT( ff_pos.default_val, ff_pos.length );
- RETURN val;
- END LOOP;
-
- RETURN NULL;
-END;
-$func$ LANGUAGE PLPGSQL;
-
CREATE OR REPLACE FUNCTION biblio.marc21_extract_fixed_field( rid BIGINT, ff TEXT ) RETURNS TEXT AS $func$
SELECT * FROM vandelay.marc21_extract_fixed_field( (SELECT marc FROM biblio.record_entry WHERE id = $1), $2 );
$func$ LANGUAGE SQL;
-CREATE TYPE biblio.record_ff_map AS (record BIGINT, ff_name TEXT, ff_value TEXT);
-CREATE OR REPLACE FUNCTION vandelay.marc21_extract_all_fixed_fields( marc TEXT ) RETURNS SETOF biblio.record_ff_map AS $func$
-DECLARE
- tag_data TEXT;
- rtype TEXT;
- ff_pos RECORD;
- output biblio.record_ff_map%ROWTYPE;
-BEGIN
- rtype := (vandelay.marc21_record_type( marc )).code;
-
- FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE rec_type = rtype ORDER BY tag DESC LOOP
- output.ff_name := ff_pos.fixed_field;
- output.ff_value := NULL;
-
- IF ff_pos.tag = 'ldr' THEN
- output.ff_value := oils_xpath_string('//*[local-name()="leader"]', marc);
- IF output.ff_value IS NOT NULL THEN
- output.ff_value := SUBSTRING( output.ff_value, ff_pos.start_pos + 1, ff_pos.length );
- RETURN NEXT output;
- output.ff_value := NULL;
- END IF;
- ELSE
- FOR tag_data IN SELECT value FROM UNNEST( oils_xpath( '//*[@tag="' || UPPER(ff_pos.tag) || '"]/text()', marc ) ) x(value) LOOP
- output.ff_value := SUBSTRING( tag_data, ff_pos.start_pos + 1, ff_pos.length );
- IF output.ff_value IS NULL THEN output.ff_value := REPEAT( ff_pos.default_val, ff_pos.length ); END IF;
- RETURN NEXT output;
- output.ff_value := NULL;
- END LOOP;
- END IF;
-
- END LOOP;
-
- RETURN;
-END;
-$func$ LANGUAGE PLPGSQL;
-
CREATE OR REPLACE FUNCTION biblio.marc21_extract_all_fixed_fields( rid BIGINT ) RETURNS SETOF biblio.record_ff_map AS $func$
SELECT $1 AS record, ff_name, ff_value FROM vandelay.marc21_extract_all_fixed_fields( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
$func$ LANGUAGE SQL;
-CREATE TYPE biblio.marc21_physical_characteristics AS ( id INT, record BIGINT, ptype TEXT, subfield INT, value INT );
-CREATE OR REPLACE FUNCTION vandelay.marc21_physical_characteristics( marc TEXT) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
-DECLARE
- rowid INT := 0;
- _007 TEXT;
- ptype config.marc21_physical_characteristic_type_map%ROWTYPE;
- psf config.marc21_physical_characteristic_subfield_map%ROWTYPE;
- pval config.marc21_physical_characteristic_value_map%ROWTYPE;
- retval biblio.marc21_physical_characteristics%ROWTYPE;
-BEGIN
-
- _007 := oils_xpath_string( '//*[@tag="007"]', marc );
-
- IF _007 IS NOT NULL AND _007 <> '' THEN
- SELECT * INTO ptype FROM config.marc21_physical_characteristic_type_map WHERE ptype_key = SUBSTRING( _007, 1, 1 );
-
- IF ptype.ptype_key IS NOT NULL THEN
- FOR psf IN SELECT * FROM config.marc21_physical_characteristic_subfield_map WHERE ptype_key = ptype.ptype_key LOOP
- SELECT * INTO pval FROM config.marc21_physical_characteristic_value_map WHERE ptype_subfield = psf.id AND value = SUBSTRING( _007, psf.start_pos + 1, psf.length );
-
- IF pval.id IS NOT NULL THEN
- rowid := rowid + 1;
- retval.id := rowid;
- retval.ptype := ptype.ptype_key;
- retval.subfield := psf.id;
- retval.value := pval.id;
- RETURN NEXT retval;
- END IF;
-
- END LOOP;
- END IF;
- END IF;
-
- RETURN;
-END;
-$func$ LANGUAGE PLPGSQL;
-
CREATE OR REPLACE FUNCTION biblio.marc21_physical_characteristics( rid BIGINT ) RETURNS SETOF biblio.marc21_physical_characteristics AS $func$
SELECT id, $1 AS record, ptype, subfield, value FROM vandelay.marc21_physical_characteristics( (SELECT marc FROM biblio.record_entry WHERE id = $1) );
$func$ LANGUAGE SQL;
-- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
IF NOT FOUND THEN
- FOR attr_def IN SELECT * FROM config.record_attr_definition ORDER BY format LOOP
-
- IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
- SELECT ARRAY_TO_STRING(ARRAY_ACCUM(value), COALESCE(attr_def.joiner,' ')) INTO attr_value
- FROM (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
- WHERE record = NEW.id
- AND tag LIKE attr_def.tag
- AND CASE
- WHEN attr_def.sf_list IS NOT NULL
- THEN POSITION(subfield IN attr_def.sf_list) > 0
- ELSE TRUE
- END
- GROUP BY tag
- ORDER BY tag
- LIMIT 1;
-
- ELSIF attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
- attr_value := biblio.marc21_extract_fixed_field(NEW.id, attr_def.fixed_field);
-
- ELSIF attr_def.xpath IS NOT NULL THEN -- and xpath expression
-
- SELECT INTO xfrm * FROM config.xml_transform WHERE name = attr_def.format;
-
- -- See if we can skip the XSLT ... it's expensive
- IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
- -- Can't skip the transform
- IF xfrm.xslt <> '---' THEN
- transformed_xml := oils_xslt_process(NEW.marc,xfrm.xslt);
- ELSE
- transformed_xml := NEW.marc;
- END IF;
-
- prev_xfrm := xfrm.name;
- END IF;
-
- IF xfrm.name IS NULL THEN
- -- just grab the marcxml (empty) transform
- SELECT INTO xfrm * FROM config.xml_transform WHERE xslt = '---' LIMIT 1;
- prev_xfrm := xfrm.name;
- END IF;
-
- attr_value := oils_xpath_string(attr_def.xpath, transformed_xml, COALESCE(attr_def.joiner,' '), ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]);
-
- ELSIF attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
- SELECT m.value INTO attr_value
- FROM biblio.marc21_physical_characteristics(NEW.id) v
- JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
- WHERE v.subfield = attr_def.phys_char_sf
- LIMIT 1; -- Just in case ...
-
- END IF;
-
- -- apply index normalizers to attr_value
- FOR normalizer IN
- SELECT n.func AS func,
- n.param_count AS param_count,
- m.params AS params
- FROM config.index_normalizer n
- JOIN config.record_attr_index_norm_map m ON (m.norm = n.id)
- WHERE attr = attr_def.name
- ORDER BY m.pos LOOP
- EXECUTE 'SELECT ' || normalizer.func || '(' ||
- quote_literal( attr_value ) ||
- CASE
- WHEN normalizer.param_count > 0
- THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
- ELSE ''
- END ||
- ')' INTO attr_value;
-
- END LOOP;
-
- -- Add the new value to the hstore
- new_attrs := new_attrs || hstore( attr_def.name, attr_value );
-
- END LOOP;
+ new_attrs := vandelay.extract_rec_attrs(NEW.marc);
IF TG_OP = 'INSERT' OR OLD.deleted THEN -- initial insert OR revivication
INSERT INTO metabib.record_attr (id, attrs) VALUES (NEW.id, new_attrs);
ELSE
- UPDATE metabib.record_attr SET attrs = attrs || new_attrs WHERE id = NEW.id;
+ UPDATE metabib.record_attr SET attrs = new_attrs WHERE id = NEW.id;
END IF;
END IF;
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (2, 'author', oils_i18n_gettext(2, 'Author of work', 'vqbrad', 'description'),'//*[@tag="100" or @tag="110" or @tag="113"]/*[contains("ad",@code)]');
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (3, 'language', oils_i18n_gettext(3, 'Language of work', 'vqbrad', 'description'),'//*[@tag="240"]/*[@code="l"][1]');
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (4, 'pagination', oils_i18n_gettext(4, 'Pagination', 'vqbrad', 'description'),'//*[@tag="300"]/*[@code="a"][1]');
-INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident, remove ) VALUES (5, 'isbn',oils_i18n_gettext(5, 'ISBN', 'vqbrad', 'description'),'//*[@tag="020"]/*[@code="a"]', TRUE, $r$(?:-|\s.+$)$r$);
-INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident, remove ) VALUES (6, 'issn',oils_i18n_gettext(6, 'ISSN', 'vqbrad', 'description'),'//*[@tag="022"]/*[@code="a"]', TRUE, $r$(?:-|\s.+$)$r$);
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, remove ) VALUES (5, 'isbn',oils_i18n_gettext(5, 'ISBN', 'vqbrad', 'description'),'//*[@tag="020"]/*[@code="a"]', $r$(?:-|\s.+$)$r$);
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, remove ) VALUES (6, 'issn',oils_i18n_gettext(6, 'ISSN', 'vqbrad', 'description'),'//*[@tag="022"]/*[@code="a"]', $r$(?:-|\s.+$)$r$);
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (7, 'price',oils_i18n_gettext(7, 'Price', 'vqbrad', 'description'),'//*[@tag="020" or @tag="022"]/*[@code="c"][1]');
-INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (8, 'rec_identifier',oils_i18n_gettext(8, 'Accession Number', 'vqbrad', 'description'),'//*[@tag="001"]', TRUE);
-INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (9, 'eg_tcn',oils_i18n_gettext(9, 'TCN Value', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="a"]', TRUE);
-INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (10, 'eg_tcn_source',oils_i18n_gettext(10, 'TCN Source', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="b"]', TRUE);
-INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, ident ) VALUES (11, 'eg_identifier',oils_i18n_gettext(11, 'Internal ID', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="c"]', TRUE);
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath) VALUES (8, 'rec_identifier',oils_i18n_gettext(8, 'Accession Number', 'vqbrad', 'description'),'//*[@tag="001"]');
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath) VALUES (9, 'eg_tcn',oils_i18n_gettext(9, 'TCN Value', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="a"]');
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath) VALUES (10, 'eg_tcn_source',oils_i18n_gettext(10, 'TCN Source', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="b"]');
+INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath) VALUES (11, 'eg_identifier',oils_i18n_gettext(11, 'Internal ID', 'vqbrad', 'description'),'//*[@tag="901"]/*[@code="c"]');
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (12, 'publisher',oils_i18n_gettext(12, 'Publisher', 'vqbrad', 'description'),'//*[@tag="260"]/*[@code="b"][1]');
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath, remove ) VALUES (13, 'pubdate',oils_i18n_gettext(13, 'Publication Date', 'vqbrad', 'description'),'//*[@tag="260"]/*[@code="c"][1]',$r$\D$r$);
INSERT INTO vandelay.bib_attr_definition ( id, code, description, xpath ) VALUES (14, 'edition',oils_i18n_gettext(14, 'Edition', 'vqbrad', 'description'),'//*[@tag="250"]/*[@code="a"][1]');
'k'
);
-INSERT INTO vandelay.authority_attr_definition (id, code, description, xpath, ident ) VALUES (1, 'rec_identifier',oils_i18n_gettext(1, 'Identifier', 'vqarad', 'description'),'//*[@tag="001"]', TRUE);
+INSERT INTO vandelay.authority_attr_definition (id, code, description, xpath) VALUES (1, 'rec_identifier',oils_i18n_gettext(1, 'Identifier', 'vqarad', 'description'),'//*[@tag="001"]');
SELECT SETVAL('vandelay.authority_attr_definition_id_seq'::TEXT, 100);
(37, 'circ_lib.billing_address')
;
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'general.unknown', oils_i18n_gettext('general.unknown', 'Import or Overlay failed', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.duplicate.barcode', oils_i18n_gettext('import.item.duplicate.barcode', 'Import failed due to barcode collision', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.invalid.circ_modifier', oils_i18n_gettext('import.item.invalid.circ_modifier', 'Import failed due to invalid circulation modifier', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.item.invalid.location', oils_i18n_gettext('import.item.invalid.location', 'Import failed due to invalid copy location', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.duplicate.sysid', oils_i18n_gettext('import.duplicate.sysid', 'Import failed due to system id collision', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.duplicate.tcn', oils_i18n_gettext('import.duplicate.sysid', 'Import failed due to system id collision', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.missing.sysid', oils_i18n_gettext('overlay.missing.sysid', 'Overlay failed due to missing system id', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.auth.duplicate.acn', oils_i18n_gettext('import.auth.duplicate.acn', 'Import failed due to Accession Number collision', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'import.xml.malformed', oils_i18n_gettext('import.xml.malformed', 'Malformed record cause Import failure', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.xml.malformed', oils_i18n_gettext('overlay.xml.malformed', 'Malformed record cause Overlay failure', 'vie', 'description') );
+INSERT INTO vandelay.import_error ( code, description ) VALUES ( 'overlay.record.quality', oils_i18n_gettext('overlay.record.quality', 'New record had insufficient quality', 'vie', 'description') );
+
INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
'ui.cat.volume_copy_editor.horizontal',
oils_i18n_gettext(
BEGIN;
CREATE SCHEMA unapi;
-CREATE OR REPLACE FUNCTION evergreen.array_remove_item_by_value(inp ANYARRAY, el ANYELEMENT) RETURNS anyarray AS $$ SELECT ARRAY_ACCUM(x.e) FROM UNNEST( $1 ) x(e) WHERE x.e <> $2; $$ LANGUAGE SQL;
-
CREATE TABLE unapi.bre_output_layout (
name TEXT PRIMARY KEY,
transform TEXT REFERENCES config.xml_transform (name) ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;
$func$ LANGUAGE SQL;
-CREATE OR REPLACE FUNCTION authority.flatten_marc ( TEXT ) RETURNS SETOF authority.full_rec AS $func$
-
-use MARC::Record;
-use MARC::File::XML (BinaryEncoding => 'UTF-8');
-use MARC::Charset;
-
-MARC::Charset->assume_unicode(1);
-
-my $xml = shift;
-my $r = MARC::Record->new_from_xml( $xml );
-
-return_next( { tag => 'LDR', value => $r->leader } );
-
-for my $f ( $r->fields ) {
- if ($f->is_control_field) {
- return_next({ tag => $f->tag, value => $f->data });
- } else {
- for my $s ($f->subfields) {
- return_next({
- tag => $f->tag,
- ind1 => $f->indicator(1),
- ind2 => $f->indicator(2),
- subfield => $s->[0],
- value => $s->[1]
- });
-
- }
- }
-}
-
-return undef;
-
-$func$ LANGUAGE PLPERLU;
-
-CREATE OR REPLACE FUNCTION authority.flatten_marc ( rid BIGINT ) RETURNS SETOF authority.full_rec AS $func$
-DECLARE
- auth authority.record_entry%ROWTYPE;
- output authority.full_rec%ROWTYPE;
- field RECORD;
-BEGIN
- SELECT INTO auth * FROM authority.record_entry WHERE id = rid;
-
- FOR field IN SELECT * FROM authority.flatten_marc( auth.marc ) LOOP
- output.record := rid;
- output.ind1 := field.ind1;
- output.ind2 := field.ind2;
- output.tag := field.tag;
- output.subfield := field.subfield;
- IF field.subfield IS NOT NULL THEN
- output.value := naco_normalize(field.value, field.subfield);
- ELSE
- output.value := field.value;
- END IF;
-
- CONTINUE WHEN output.value IS NULL;
-
- RETURN NEXT output;
- END LOOP;
-END;
-$func$ LANGUAGE PLPGSQL;
-
-- authority.rec_descriptor appears to be unused currently
CREATE OR REPLACE FUNCTION authority.reingest_authority_rec_descriptor( auth_id BIGINT ) RETURNS VOID AS $func$
BEGIN
--- /dev/null
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('test'); -- phasefx
+
+INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES (
+ 'vandelay.queued_bib_record.print',
+ 'vqbr',
+ oils_i18n_gettext(
+ 'vandelay.queued_bib_record.print',
+ 'Print output has been requested for records in an Importer Bib Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.queued_bib_record.csv',
+ 'vqbr',
+ oils_i18n_gettext(
+ 'vandelay.queued_bib_record.csv',
+ 'CSV output has been requested for records in an Importer Bib Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.queued_bib_record.email',
+ 'vqbr',
+ oils_i18n_gettext(
+ 'vandelay.queued_bib_record.email',
+ 'An email has been requested for records in an Importer Bib Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.queued_auth_record.print',
+ 'vqar',
+ oils_i18n_gettext(
+ 'vandelay.queued_auth_record.print',
+ 'Print output has been requested for records in an Importer Authority Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.queued_auth_record.csv',
+ 'vqar',
+ oils_i18n_gettext(
+ 'vandelay.queued_auth_record.csv',
+ 'CSV output has been requested for records in an Importer Authority Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.queued_auth_record.email',
+ 'vqar',
+ oils_i18n_gettext(
+ 'vandelay.queued_auth_record.email',
+ 'An email has been requested for records in an Importer Authority Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.import_items.print',
+ 'vii',
+ oils_i18n_gettext(
+ 'vandelay.import_items.print',
+ 'Print output has been requested for Import Items from records in an Importer Bib Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.import_items.csv',
+ 'vii',
+ oils_i18n_gettext(
+ 'vandelay.import_items.csv',
+ 'CSV output has been requested for Import Items from records in an Importer Bib Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+ ,(
+ 'vandelay.import_items.email',
+ 'vii',
+ oils_i18n_gettext(
+ 'vandelay.import_items.email',
+ 'An email has been requested for Import Items from records in an Importer Bib Queue.',
+ 'ath',
+ 'description'
+ ),
+ FALSE
+ )
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 38,
+ TRUE,
+ 1,
+ 'Print Output for Queued Bib Records',
+ 'vandelay.queued_bib_record.print',
+ 'NOOP_True',
+ 'ProcessTemplate',
+ 'queue.owner',
+ 'print-on-demand',
+$$
+[%- USE date -%]
+<pre>
+Queue ID: [% target.0.queue.id %]
+Queue Name: [% target.0.queue.name %]
+Queue Type: [% target.0.queue.queue_type %]
+Complete? [% target.0.queue.complete %]
+
+ [% FOR vqbr IN target %]
+=-=-=
+ Title of work | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
+ Author of work | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
+ Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
+ Pagination | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
+ ISBN | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
+ ISSN | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
+ Price | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
+ Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
+ TCN Value | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
+ TCN Source | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
+ Internal ID | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
+ Publisher | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
+ Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
+ Edition | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
+ Item Barcode | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
+
+ [% END %]
+</pre>
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 38, 'attributes')
+ ,( 38, 'queue')
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 39,
+ TRUE,
+ 1,
+ 'CSV Output for Queued Bib Records',
+ 'vandelay.queued_bib_record.csv',
+ 'NOOP_True',
+ 'ProcessTemplate',
+ 'queue.owner',
+ 'print-on-demand',
+$$
+[%- USE date -%][%- FOR vqbr IN target -%]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]"[%- END -%]
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 39, 'attributes')
+ ,( 39, 'queue')
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 40,
+ TRUE,
+ 1,
+ 'Email Output for Queued Bib Records',
+ 'vandelay.queued_bib_record.email',
+ 'NOOP_True',
+ 'SendEmail',
+ 'queue.owner',
+ NULL,
+$$
+[%- USE date -%]
+[%- SET user = target.0.queue.owner -%]
+To: [%- params.recipient_email || user.email || 'root@localhost' %]
+From: [%- params.sender_email || default_sender %]
+Subject: Bibs from Import Queue
+
+Queue ID: [% target.0.queue.id %]
+Queue Name: [% target.0.queue.name %]
+Queue Type: [% target.0.queue.queue_type %]
+Complete? [% target.0.queue.complete %]
+
+ [% FOR vqbr IN target %]
+=-=-=
+ Title of work | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
+ Author of work | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
+ Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
+ Pagination | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
+ ISBN | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
+ ISSN | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
+ Price | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
+ Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
+ TCN Value | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
+ TCN Source | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
+ Internal ID | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
+ Publisher | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
+ Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
+ Edition | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
+ Item Barcode | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
+
+ [% END %]
+
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 40, 'attributes')
+ ,( 40, 'queue')
+ ,( 40, 'queue.owner')
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 41,
+ TRUE,
+ 1,
+ 'Print Output for Queued Authority Records',
+ 'vandelay.queued_auth_record.print',
+ 'NOOP_True',
+ 'ProcessTemplate',
+ 'queue.owner',
+ 'print-on-demand',
+$$
+[%- USE date -%]
+<pre>
+Queue ID: [% target.0.queue.id %]
+Queue Name: [% target.0.queue.name %]
+Queue Type: [% target.0.queue.queue_type %]
+Complete? [% target.0.queue.complete %]
+
+ [% FOR vqar IN target %]
+=-=-=
+ Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
+
+ [% END %]
+</pre>
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 41, 'attributes')
+ ,( 41, 'queue')
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 42,
+ TRUE,
+ 1,
+ 'CSV Output for Queued Authority Records',
+ 'vandelay.queued_auth_record.csv',
+ 'NOOP_True',
+ 'ProcessTemplate',
+ 'queue.owner',
+ 'print-on-demand',
+$$
+[%- USE date -%][%- FOR vqar IN target -%]"[% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) | replace('"', '""') %]"[%- END -%]
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 42, 'attributes')
+ ,( 42, 'queue')
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 43,
+ TRUE,
+ 1,
+ 'Email Output for Queued Authority Records',
+ 'vandelay.queued_auth_record.email',
+ 'NOOP_True',
+ 'SendEmail',
+ 'queue.owner',
+ NULL,
+$$
+[%- USE date -%]
+[%- SET user = target.0.queue.owner -%]
+To: [%- params.recipient_email || user.email || 'root@localhost' %]
+From: [%- params.sender_email || default_sender %]
+Subject: Authorities from Import Queue
+
+Queue ID: [% target.0.queue.id %]
+Queue Name: [% target.0.queue.name %]
+Queue Type: [% target.0.queue.queue_type %]
+Complete? [% target.0.queue.complete %]
+
+ [% FOR vqar IN target %]
+=-=-=
+ Record Identifier | [% helpers.get_queued_auth_attr('rec_identifier',vqar.attributes) %]
+
+ [% END %]
+
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 43, 'attributes')
+ ,( 43, 'queue')
+ ,( 43, 'queue.owner')
+;
+
+INSERT INTO action_trigger.event_definition (
+ id,
+ active,
+ owner,
+ name,
+ hook,
+ validator,
+ reactor,
+ group_field,
+ granularity,
+ template
+ ) VALUES (
+ 44,
+ TRUE,
+ 1,
+ 'Print Output for Import Items from Queued Bib Records',
+ 'vandelay.import_items.print',
+ 'NOOP_True',
+ 'ProcessTemplate',
+ 'record.queue.owner',
+ 'print-on-demand',
+$$
+[%- USE date -%]
+<pre>
+Queue ID: [% target.0.record.queue.id %]
+Queue Name: [% target.0.record.queue.name %]
+Queue Type: [% target.0.record.queue.queue_type %]
+Complete? [% target.0.record.queue.complete %]
+
+ [% FOR vii IN target %]
+=-=-=
+ Import Item ID | [% vii.id %]
+ Title of work | [% helpers.get_queued_bib_attr('title',vii.record.attributes) %]
+ ISBN | [% helpers.get_queued_bib_attr('isbn',vii.record.attributes) %]
+ Attribute Definition | [% vii.definition %]
+ Import Error | [% vii.import_error %]
+ Import Error Detail | [% vii.error_detail %]
+ Owning Library | [% vii.owning_lib %]
+ Circulating Library | [% vii.circ_lib %]
+ Call Number | [% vii.call_number %]
+ Copy Number | [% vii.copy_number %]
+ Status | [% vii.status.name %]
+ Shelving Location | [% vii.location.name %]
+ Circulate | [% vii.circulate %]
+ Deposit | [% vii.deposit %]
+ Deposit Amount | [% vii.deposit_amount %]
+ Reference | [% vii.ref %]
+ Holdable | [% vii.holdable %]
+ Price | [% vii.price %]
+ Barcode | [% vii.barcode %]
+ Circulation Modifier | [% vii.circ_modifier %]
+ Circulate As MARC Type | [% vii.circ_as_type %]
+ Alert Message | [% vii.alert_message %]
+ Public Note | [% vii.pub_note %]
+ Private Note | [% vii.priv_note %]
+ OPAC Visible | [% vii.opac_visible %]
+
+ [% END %]
+</pre>
+$$
+ )
+;
+
+INSERT INTO action_trigger.environment ( event_def, path) VALUES (
+ 44, 'record')
+ ,( 44, 'record.attributes')
+ ,( 44, 'record.queue')
+ ,( 44, 'record.queue.owner')
+;
+
+COMMIT;
+
+-- BEGIN; DELETE FROM action_trigger.event_output WHERE id IN ((SELECT template_output FROM action_trigger.event WHERE event_def IN (38,39,40,41,42,43,44))UNION(SELECT error_output FROM action_trigger.event WHERE event_def IN (38,39,40,41,42,43,44))); DELETE FROM action_trigger.event WHERE event_def IN (38,39,40,41,42,43,44); DELETE FROM action_trigger.environment WHERE event_def IN (38,39,40,41,42,43,44); DELETE FROM action_trigger.event_definition WHERE id IN (38,39,40,41,42,43,44); DELETE FROM action_trigger.hook WHERE key IN ('vandelay.queued_bib_record.print','vandelay.queued_bib_record.csv','vandelay.queued_bib_record.email','vandelay.queued_auth_record.print','vandelay.queued_auth_record.csv','vandelay.queued_auth_record.email','vandelay.import_items.print','vandelay.import_items.csv','vandelay.import_items.email'); DELETE FROM config.upgrade_log WHERE version = 'test'; COMMIT;
.export_tr_border td { border-top: 1px solid #808080; }
.nav_row_div {padding:1px; text-align:center; }
.toolbar_selected { border: 2px dashed #808080; text-decoration:underline; font-weight:bold;}
+
+#vl-import-error-record { margin: 10px; }
+#vl-import-error-record tr:nth-child(even) {background: #EEE}
+#vl-import-error-record td:first-child { text-decoration: underline; }
+#vl-import-error-record td { padding: 5px; }
+#vl-error-import-error { font-weight: bold; }
+
+#vl-queue-filter-fieldset { padding-top: 3px; border:2px dashed #d9e8f9; }
+#vl-queue-filter-fieldset { margin: 5px; }
+#vl-queue-filter-fieldset td { padding: 5px; }
+#vl-queue-filter-fieldset th { padding: 5px; }
+#vl-queue-filter-fieldset legend { font-weight: bold; font-size: 1.3em }
+
+#vl-queue-filter-table { margin-left: 15px; padding-left: 15px; border-left: 2px dashed #d9e8f9;}
+#vl-queue-summary-table { margin-left: 15px; padding-left: 15px; border-left: 2px dashed #d9e8f9;}
+.queue-nav-table tr:nth-child(even) {background: #EEE}
+.queue-nav-table-label { font-weight: bold; text-decoration:underline;}
+.queue-pager-span { padding-right: 5px; margin-right: 5px; border-right: 2px solid #e8e1cf; }
+#vl-queue-paging-table td { padding-bottom: 0px; }
+#vl-file-label { margin-right: 10px; }
return true;
}
return;
+ },
+
+ toString : function() {
+ /* ever so slightly aid debugging */
+ if (this.classname)
+ return "[object fieldmapper." + this.classname + "]";
+ else
+ return Object.prototype.toString();
}
});
--- /dev/null
+dojo.provide("openils.vandelay.TreeDndSource");
+dojo.require("dijit._tree.dndSource");
+
+/* This class specifically serves the eg/vandelay/match_set interface
+ * for editing Vandelay Match Set trees. It should probably have a more
+ * specific name that reflects that.
+ */
+dojo.declare(
+ "openils.vandelay.TreeDndSource", dijit._tree.dndSource, {
+ "_is_replaceable": function(spoint, dpoint, disroot) {
+ /* An OP can replace anything, but non-OPs can only replace other
+ * non-OPs.
+ */
+ if (spoint.bool_op())
+ return true;
+ else if (!dpoint.bool_op())
+ return true;
+ return false;
+ },
+ "constructor": function() {
+ /* Given a tree object, there seems to be no way to access its
+ * dndController, which seems to be the only thing that knows
+ * about a tree's selected nodes. So we register instances
+ * in a global variable in order to find them later. :-(
+ */
+ if (!window._tree_dnd_controllers)
+ window._tree_dnd_controllers = [];
+
+ window._tree_dnd_controllers.push(this);
+
+ dojo.connect(
+ this.tree.model.store, "onNew", this,
+ function() { this.redraw_expression_preview(); }
+ );
+ dojo.connect(
+ this.tree.model.store, "onDelete", this,
+ function() { this.redraw_expression_preview(); }
+ );
+ },
+ "redraw_expression_preview": function() {
+ if (typeof(window.redraw_expression_preview) == "function") {
+ window.redraw_expression_preview();
+ } else {
+ console.log("no redraw_expression_preview function registered");
+ }
+ },
+ "checkItemAcceptance": function(target, source, position) {
+ if (!source._ready || source == this) return;
+
+ if (this.tree.model.replace_mode) {
+ var ditem = dijit.getEnclosingWidget(target).item;
+ return (
+ position == "over" && this._is_replaceable(
+ source.getAllNodes()[0].match_point,
+ this.tree.model.store.getValue(ditem, "match_point"),
+ ditem === this.tree.model.root
+ )
+ );
+ } else {
+ return (
+ position != "over" ||
+ this.tree.model.mayHaveChildren(
+ dijit.getEnclosingWidget(target).item
+ )
+ );
+ }
+ /* code in match_set.js makes sure that source._ready gets set true
+ * only when we want the item to be draggable */
+ },
+ "itemCreator": function(nodes, somethingelse) {
+ var default_items = this.inherited(arguments);
+ for (var i = 0; i < default_items.length; i++)
+ default_items[i].match_point = nodes[i].match_point;
+ return default_items;
+ },
+ "onDndDrop": function(source, nodes, copy) {
+ if (
+ !this.tree.model.replace_mode ||
+ this.containerState != "Over" ||
+ this.dropPosition == "Before" ||
+ this.dropPosition == "After" ||
+ source == this
+ ) {
+ return this.inherited(arguments);
+ }
+
+ /* This method only comes into play for our "replace mode" */
+
+ var target_widget = dijit.getEnclosingWidget(this.targetAnchor);
+ var new_params = this.itemCreator(nodes, this.targetAnchor)[0];
+
+ /* Here, we morph target_widget.item into the new item */
+
+ var store = this.tree.model.store;
+ var item = target_widget.item;
+ for (var k in new_params) {
+ if (k == "id") continue; /* can't use this / don't need it */
+ store.setValue(item, k, new_params[k]);
+ }
+ if (this.tree.model.root === item) { /* replacing root node */
+ if (!new_params.match_point.bool_op()) {
+ /* If we're here, we've replaced the root node with
+ * something that isn't a bool op, so we need to nuke
+ * any children that the item has.
+ */
+ store.setValue(item, "children", []);
+ }
+ }
+ if (typeof(window.render_vmsp_label) == "function") {
+ store.setValue(
+ item,
+ "name",
+ window.render_vmsp_label(new_params.match_point)
+ );
+ }
+
+ this.redraw_expression_preview();
+
+ /* just because this is at the end of the default implementation: */
+ this.onDndCancel();
+ }
+ }
+);
--- /dev/null
+dojo.provide("openils.vandelay.TreeStoreModel");
+dojo.require("dijit.tree.TreeStoreModel");
+dojo.require("openils.Util");
+
+/* This class specifically serves the eg/vandelay/match_set interface
+ * for editing Vandelay Match Set trees. It should probably have a more
+ * specific name that reflects that.
+ */
+
+function _simple_item(model, item) {
+ /* Instead of model.getLabel(), could do
+ * model.store.getValue(item, "blah") or something like that ... */
+ var mp = model.store.getValue(item, "match_point");
+ mp.children([]);
+ return mp;
+}
+
+dojo.declare(
+ "openils.vandelay.TreeStoreModel", dijit.tree.TreeStoreModel, {
+ "replace_mode": 0,
+ "get_simple_tree": function(item, oncomplete, result) {
+ var self = this;
+ var me;
+ if (!result) {
+ me = result = _simple_item(this, item);
+ } else {
+ me = _simple_item(this, item);
+ result.push(me);
+ }
+
+ if (this.mayHaveChildren(item)) {
+ this.getChildren(
+ item, function(children) {
+ var kids_here = [];
+ for (var i = 0; i < children.length; i++) {
+ self.get_simple_tree(children[i], null, kids_here);
+ }
+ me.children(kids_here);
+ if (oncomplete) oncomplete(result);
+ }
+ );
+ }
+ },
+ "mayHaveChildren": function(item) {
+ var match_point = this.store.getValue(item, "match_point");
+ if (match_point)
+ return openils.Util.isTrue(match_point.bool_op());
+ else
+ return true;
+ }
+ }
+);
--- /dev/null
+{
+ "DEFINE_MP": "Define this match point using the above fields, then drag me to the tree on the right.",
+ "LEAVE_ROOT_ALONE": "You cannot delete the root node of a tree (but you can replace it).",
+ "EXACTLY_ONE": "First select exactly one node from the tree on the right side of the screen.",
+ "EXIT_REPLACE_MODE": "Exit Replace Mode",
+ "ENTER_REPLACE_MODE": "Enter Replace Mode",
+ "NO_CAN_DO": "An error has occurred. Close this interface and try again.",
+ "OK": "Ok",
+ "CANCEL": "Cancel",
+ "POINT_NEEDS_ONE": "You have not entered valid data",
+ "FAULTY_MARC": "A MARC tag must be identified by three digits, and the subfield must be one non-whitespace, non-control character.",
+ "WORKING_MP_HERE": "Choose from among the three buttons above to add a new match point.",
+ "WORKING_QM_HERE": "Use buttons above and to the right to add new quality metrics.",
+ "SVF": "Record Attribute"
+}
*/
return autoWidget.getDisplayString();
- }
+ };
+
+ openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
+ if (!item) return "";
+ return fieldmapper.aou.findOrgUnit(
+ this.grid.store.getValue(item, this.field)
+ ).shortname();
+ };
}
--- /dev/null
+dojo.require("dijit.Tree");
+dojo.require("dijit.form.Button");
+dojo.require("dojo.data.ItemFileWriteStore");
+dojo.require("dojo.dnd.Source");
+dojo.require("openils.vandelay.TreeDndSource");
+dojo.require("openils.vandelay.TreeStoreModel");
+dojo.require("openils.CGI");
+dojo.require("openils.User");
+dojo.require("openils.Util");
+dojo.require("openils.PermaCrud");
+dojo.require("openils.widget.ProgressDialog");
+dojo.require("openils.widget.AutoGrid");
+
+var localeStrings, node_editor, qnode_editor, _crads, CGI, tree, match_set;
+
+var NodeEditorAbstract = {
+ "_svf_select_template": null,
+ "_simple_value_getter": function(control) {
+ if (typeof control.selectedIndex != "undefined")
+ return control.options[control.selectedIndex].value;
+ else if (dojo.attr(control, "type") == "checkbox")
+ return control.checked;
+ else
+ return control.value;
+ },
+ "is_sensible": function(thing) {
+ var need_one = 0;
+ this.foi.forEach(function(field) { if (thing[field]()) need_one++; });
+
+ if (need_one != 1) {
+ alert(localeStrings.POINT_NEEDS_ONE);
+ return false;
+ }
+
+ if (thing.tag()) {
+ if (
+ !thing.tag().match(/^\d{3}$/) ||
+ thing.subfield().length != 1 ||
+ !thing.subfield().match(/\S/) ||
+ thing.subfield().charCodeAt(0) < 32
+ ) {
+ alert(localeStrings.FAULTY_MARC);
+ return false;
+ }
+ }
+
+ return true;
+ },
+ "_add_consistent_controls": function(tgt) {
+ if (!this._consistent_controls) {
+ var trs = dojo.query(this._consistent_controls_query);
+ this._consistent_controls = [];
+ for (var i = 0; i < trs.length; i++)
+ this._consistent_controls[i] = dojo.clone(trs[i]);
+ }
+
+ this._consistent_controls.forEach(
+ function(node) { dojo.place(dojo.clone(node), tgt); }
+ );
+ },
+ "_factories_by_type": {
+ "svf": function() {
+ if (!self._svf_select_template) {
+ self._svf_select_template = dojo.create(
+ "select", {"fmfield": "svf"}
+ );
+ for (var i=0; i<_crads.length; i++) {
+ dojo.create(
+ "option", {
+ "value": _crads[i].name(),
+ "innerHTML": _crads[i].label()
+ }, self._svf_select_template
+ );
+ }
+ }
+
+ var select = dojo.clone(self._svf_select_template);
+ dojo.attr(select, "id", "svf-select");
+ var label = dojo.create(
+ "label", {
+ "for": "svf-select", "innerHTML": localeStrings.SVF + ":"
+ }
+ );
+
+ var tr = dojo.create("tr");
+ dojo.place(label, dojo.create("td", null, tr));
+ dojo.place(select, dojo.create("td", null, tr));
+
+ return [tr];
+ },
+ "tag": function() {
+ var rows = [dojo.create("tr"), dojo.create("tr")];
+ dojo.create(
+ "label", {
+ "for": "tag-input", "innerHTML": "Tag:"
+ }, dojo.create("td", null, rows[0])
+ );
+ dojo.create(
+ "input", {
+ "id": "tag-input",
+ "type": "text",
+ "size": 4,
+ "maxlength": 3,
+ "fmfield": "tag"
+ }, dojo.create("td", null, rows[0])
+ );
+ dojo.create(
+ "label", {
+ "for": "subfield-input", "innerHTML": "Subfield: \u2021"
+ }, dojo.create("td", null, rows[1])
+ );
+ dojo.create(
+ "input", {
+ "id": "subfield-input",
+ "type": "text",
+ "size": 2,
+ "maxlength": 1,
+ "fmfield": "subfield"
+ }, dojo.create("td", null, rows[1])
+ );
+ return rows;
+ },
+ "bool_op": function() {
+ var tr = dojo.create("tr");
+ dojo.create(
+ "label",
+ {"for": "operator-select", "innerHTML": "Operator:"},
+ dojo.create("td", null, tr)
+ );
+ var select = dojo.create(
+ "select", {"fmfield": "bool_op", "id": "operator-select"},
+ dojo.create("td", null, tr)
+ );
+ dojo.create("option", {"value": "AND", "innerHTML": "AND"}, select);
+ dojo.create("option", {"value": "OR", "innerHTML": "OR"}, select);
+
+ return [tr];
+ }
+ }
+};
+
+function apply_base_class(cls, basecls) {
+ openils.Util.objectProperties(basecls).forEach(
+ function(m) { cls[m] = basecls[m]; }
+ );
+}
+
+function QualityNodeEditor() {
+ var self = this;
+ this.foi = ["tag", "svf"]; /* Fields of Interest - starting points for UI */
+
+ this._init = function(qnode_editor_container) {
+ this._consistent_controls_query =
+ "[consistent-controls], [quality-controls]";
+ this.qnode_editor_container = dojo.byId(qnode_editor_container);
+ this.clear();
+ };
+
+ this.clear = function() {
+ dojo.create(
+ "em", {"innerHTML": localeStrings.WORKING_QM_HERE},
+ this.qnode_editor_container, "only"
+ );
+ };
+
+ this.build_vmsq = function() {
+ var metric = new vmsq();
+ metric.match_set(match_set.id()); /* using global */
+ var controls = dojo.query("[fmfield]", this.qnode_editor_container);
+ for (var i = 0; i < controls.length; i++) {
+ var field = dojo.attr(controls[i], "fmfield");
+ var value = this._simple_value_getter(controls[i]);
+ metric[field](value);
+ }
+
+ if (!this.is_sensible(metric)) return null; /* will alert() */
+ else return metric;
+ };
+
+ this.add = function(type) {
+ this.clear();
+
+ /* these are the editing widgets */
+ var table = dojo.create("table", {"className": "node-editor"});
+
+ var nodes = this._factories_by_type[type]();
+ for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
+
+ this._add_consistent_controls(table);
+
+ var ok_cxl_td = dojo.create(
+ "td", {"colspan": 2, "align": "center", "className": "space-me"},
+ dojo.create("tr", null, table)
+ );
+
+ dojo.create(
+ "input", {
+ "type": "submit", "value": localeStrings.OK,
+ "onclick": function() {
+ var metric = self.build_vmsq();
+ if (metric) {
+ self.clear();
+ pcrud.create(
+ metric, {
+ /* borrowed from openils.widget.AutoGrid */
+ "oncomplete": function(req, cudResults) {
+ var fmObject = cudResults[0];
+ if (vmsq_grid.onPostCreate)
+ vmsq_grid.onPostCreate(fmObject);
+ if (fmObject) {
+ vmsq_grid.store.newItem(
+ fmObject.toStoreItem()
+ );
+ }
+ setTimeout(function() {
+ try {
+ vmsq_grid.selection.select(vmsq_grid.rowCount-1);
+ vmsq_grid.views.views[0].getCellNode(vmsq_grid.rowCount-1, 1).focus();
+ } catch (E) {}
+ },200);
+ }
+ }
+ );
+ }
+ }
+ }, ok_cxl_td
+ );
+ dojo.create(
+ "input", {
+ "type": "reset", "value": localeStrings.CANCEL,
+ "onclick": function() { self.clear(); }
+ }, ok_cxl_td
+ );
+
+ dojo.place(table, this.qnode_editor_container, "only");
+
+ /* nice */
+ try { dojo.query("select, input", table)[0].focus(); }
+ catch(E) { console.log(String(E)); }
+
+ };
+
+ apply_base_class(self, NodeEditorAbstract);
+ this._init.apply(this, arguments);
+}
+
+function NodeEditor() {
+ var self = this;
+ this.foi = ["tag", "svf", "bool_op"]; /* Fields of Interest - starting points for UI */
+
+ this._init = function(dnd_source, node_editor_container) {
+ this._consistent_controls_query =
+ "[consistent-controls], [point-controls]";
+ this.dnd_source = dnd_source;
+ this.node_editor_container = dojo.byId(node_editor_container);
+ };
+
+ this.clear = function() {
+ this.dnd_source.selectAll().deleteSelectedNodes();
+ dojo.create(
+ "em", {"innerHTML": localeStrings.WORKING_MP_HERE},
+ this.node_editor_container, "only"
+ );
+ this.dnd_source._ready = false;
+ };
+
+ this.build_vmsp = function() {
+ var match_point = new vmsp();
+ var controls = dojo.query("[fmfield]", this.node_editor_container);
+ for (var i = 0; i < controls.length; i++) {
+ var field = dojo.attr(controls[i], "fmfield");
+ var value = this._simple_value_getter(controls[i]);
+ match_point[field](value);
+ }
+
+ if (!this.is_sensible(match_point)) return null; /* will alert() */
+ else return match_point;
+ };
+
+ this.update_draggable = function(draggable) {
+ var mp;
+
+ if (!(mp = this.build_vmsp())) return; /* will alert() */
+
+ draggable.match_point = mp;
+ dojo.attr(draggable, "innerHTML", render_vmsp_label(mp));
+ this.dnd_source._ready = true;
+ };
+
+ this.add = function(type) {
+ this.clear();
+
+ /* a representation, not the editing widgets, but will also carry
+ * the fieldmapper object when dragged to the tree */
+ var draggable = dojo.create(
+ "li", {"innerHTML": localeStrings.DEFINE_MP}
+ );
+
+ /* these are the editing widgets */
+ var table = dojo.create("table", {"className": "node-editor"});
+
+ var nodes = this._factories_by_type[type]();
+ for (var i = 0; i < nodes.length; i++) dojo.place(nodes[i], table);
+
+ if (type != "bool_op")
+ this._add_consistent_controls(table);
+
+ dojo.create(
+ "input", {
+ "type": "submit", "value": localeStrings.OK,
+ "onclick": function() { self.update_draggable(draggable); }
+ }, dojo.create(
+ "td", {"colspan": 2, "align": "center"},
+ dojo.create("tr", null, table)
+ )
+ );
+
+ dojo.place(table, this.node_editor_container, "only");
+
+ this.dnd_source.insertNodes(false, [draggable]);
+
+ /* nice */
+ try { dojo.query("select, input", table)[0].focus(); }
+ catch(E) { console.log(String(E)); }
+
+ };
+
+ apply_base_class(self, NodeEditorAbstract);
+
+ this._init.apply(this, arguments);
+}
+
+function find_crad_by_name(name) {
+ for (var i = 0; i < _crads.length; i++) {
+ if (_crads[i].name() == name)
+ return _crads[i];
+ }
+ return null;
+}
+
+function render_vmsp_label(point, minimal) {
+ /* "minimal" has this implication:
+ * for svf, only show the code, not the longer label.
+ */
+ if (point.bool_op()) {
+ return point.bool_op();
+ } else if (point.svf()) {
+ return (openils.Util.isTrue(point.negate()) ? "NOT " : "") + (
+ minimal ? point.svf() :
+ (point.svf() + " / " + find_crad_by_name(point.svf()).label())
+ );
+ } else {
+ return (openils.Util.isTrue(point.negate()) ? "NOT " : "") +
+ point.tag() + " \u2021" + point.subfield();
+ }
+}
+
+function replace_mode(explicit) {
+ if (typeof explicit == "undefined")
+ tree.model.replace_mode ^= 1;
+ else
+ tree.model.replace_mode = explicit;
+
+ dojo.attr(
+ "replacer", "innerHTML",
+ localeStrings[
+ (tree.model.replace_mode ? "EXIT" : "ENTER") + "_REPLACE_MODE"
+ ]
+ );
+ dojo[tree.model.replace_mode ? "addClass" : "removeClass"](
+ "replacer", "replace-mode"
+ );
+}
+
+function delete_selected_in_tree() {
+ /* relies on the fact that we only have one tree that would have
+ * registered a dnd controller. */
+ _tree_dnd_controllers[0].getSelectedItems().forEach(
+ function(item) {
+ if (item === tree.model.root)
+ alert(localeStrings.LEAVE_ROOT_ALONE);
+ else
+ tree.model.store.deleteItem(item);
+ }
+ );
+}
+
+function new_match_set_tree() {
+ var point = new vmsp();
+ point.bool_op("AND");
+ return [
+ {
+ "id": "root",
+ "children": [],
+ "name": render_vmsp_label(point),
+ "match_point": point
+ }
+ ];
+}
+
+/* dojoize_match_set_tree() takes an argument, "point", that is actually a
+ * vmsp fieldmapper object with descendants fleshed hierarchically. It turns
+ * that into a syntactically flat array but preserving the hierarchy
+ * semantically in the language used by dojo data stores, i.e.,
+ *
+ * [
+ * {'id': 'root', children:[{'_reference': '0'}, {'_reference': '1'}]},
+ * {'id': '0', children:[]},
+ * {'id': '1', children:[]}
+ * ],
+ *
+ */
+function dojoize_match_set_tree(point, refgen) {
+ var root = false;
+ if (!refgen) {
+ if (!point) {
+ return new_match_set_tree();
+ }
+ refgen = 0;
+ root = true;
+ }
+
+ var bathwater = point.children();
+ point.children([]);
+ var item = {
+ "id": (root ? "root" : refgen),
+ "name": render_vmsp_label(point),
+ "match_point": point.clone(),
+ "children": []
+ };
+ point.children(bathwater);
+
+ var results = [item];
+
+ if (point.children()) {
+ for (var i = 0; i < point.children().length; i++) {
+ var child = point.children()[i];
+ item.children.push({"_reference": ++refgen});
+ results = results.concat(
+ dojoize_match_set_tree(child, refgen)
+ );
+ }
+ }
+
+ return results;
+}
+
+function render_vms_metadata(match_set) {
+ dojo.byId("vms-name").innerHTML = match_set.name();
+ dojo.byId("vms-owner").innerHTML =
+ aou.findOrgUnit(match_set.owner()).name();
+ dojo.byId("vms-mtype").innerHTML = match_set.mtype();
+}
+
+function redraw_expression_preview() {
+ tree.model.getRoot(
+ function(root) {
+ tree.model.get_simple_tree(
+ root, function(r) {
+ dojo.attr(
+ "expr-preview",
+ "innerHTML",
+ render_expression_preview(r)
+ );
+ }
+ );
+ }
+ );
+}
+
+function render_expression_preview(r) {
+ if (r.children().length) {
+ return "(" + r.children().map(render_expression_preview).join(
+ " " + render_vmsp_label(r) + " "
+ ) + ")";
+ } else if (!r.bool_op()) {
+ return render_vmsp_label(r, true /* minimal */);
+ } else {
+ return "()";
+ }
+}
+
+function save_tree() {
+ progress_dialog.show(true);
+
+ tree.model.getRoot(
+ function(root) {
+ tree.model.get_simple_tree(
+ root, function(r) {
+ fieldmapper.standardRequest(
+ ["open-ils.vandelay",
+ "open-ils.vandelay.match_set.update"], {
+ "params": [
+ openils.User.authtoken, match_set.id(), r
+ ],
+ "async": true,
+ "oncomplete": function(r) {
+ progress_dialog.hide();
+ /* catch exceptions */
+ r = openils.Util.readResponse(r);
+
+ location.href = location.href;
+ }
+ }
+ );
+ }
+ );
+ }
+ );
+}
+
+function init_vmsq_grid() {
+ vmsq_grid.loadAll(
+ {"order_by": {"vmsq": "quality"}},
+ {"match_set": match_set.id()}
+ );
+}
+
+function my_init() {
+ progress_dialog.show(true);
+
+ dojo.requireLocalization("openils.vandelay", "match_set");
+ localeStrings = dojo.i18n.getLocalization("openils.vandelay", "match_set");
+
+ pcrud = new openils.PermaCrud();
+ CGI = new openils.CGI();
+
+ if (!CGI.param("match_set")) {
+ alert(localeStrings.NO_CAN_DO);
+ progress_dialog.hide();
+ return;
+ }
+
+ render_vms_metadata(
+ match_set = pcrud.retrieve("vms", CGI.param("match_set"))
+ );
+
+ /* No-one should have hundreds of these or anything, but theoretically
+ * this could be problematic with a big enough list of crad objects. */
+ _crads = pcrud.retrieveAll("crad", {"order_by": {"crad": "label"}});
+
+ var match_set_tree = fieldmapper.standardRequest(
+ ["open-ils.vandelay", "open-ils.vandelay.match_set.get_tree"],
+ [openils.User.authtoken, CGI.param("match_set")]
+ );
+
+ var store = new dojo.data.ItemFileWriteStore({
+ "data": {
+ "identifier": "id",
+ "label": "name",
+ "items": dojoize_match_set_tree(match_set_tree)
+ }
+ });
+
+ var tree_model = new openils.vandelay.TreeStoreModel({
+ "store": store, "query": {"id": "root"}
+ });
+
+ var src = new dojo.dnd.Source("src-here");
+ tree = new dijit.Tree(
+ {
+ "model": tree_model,
+ "dndController": openils.vandelay.TreeDndSource,
+ "dragThreshold": 8,
+ "betweenThreshold": 5,
+ "persist": false
+ }, "tree-here"
+ );
+
+ node_editor = new NodeEditor(src, "node-editor-container");
+ qnode_editor = new QualityNodeEditor("qnode-editor-container");
+
+ replace_mode(0);
+
+ dojo.connect(
+ src, "onDndDrop", null,
+ function(source, nodes, copy, target) {
+ /* Because of the... interesting... characteristics of DnD
+ * design in dojo/dijit (at least as of 1.3), this callback will
+ * fire both for our working node dndSource and for the tree!
+ */
+ if (source == this)
+ node_editor.clear(); /* ... because otherwise this acts like a
+ copy operation no matter what the user
+ does, even though we really want a
+ "move." */
+ }
+ );
+
+ redraw_expression_preview();
+ node_editor.clear();
+
+ init_vmsq_grid();
+
+ progress_dialog.hide();
+}
+
+openils.Util.addOnLoad(my_init);
'vl-attr-editor-div',
'vl-marc-export-div',
'vl-profile-editor-div',
- 'vl-item-attr-editor-div'
+ 'vl-item-attr-editor-div',
+ 'vl-import-error-div'
];
var authtoken;
var vlQueueGridColumePicker = {};
var vlBibSources = [];
var importItemDefs = [];
+var matchSets = {};
+var mergeProfiles = [];
/**
* Grab initial data
*/
function vlInit() {
authtoken = openils.User.authtoken;
- var initNeeded = 6; // how many async responses do we need before we're init'd
+ var initNeeded = 7; // how many async responses do we need before we're init'd
var initCount = 0; // how many async reponses we've received
openils.Util.registerEnterHandler(
runStartupCommands();
}
- var profiles = new openils.PermaCrud().retrieveAll('vmp');
- vlUploadMergeProfile.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(profiles)});
+ mergeProfiles = new openils.PermaCrud().retrieveAll('vmp');
+ vlUploadMergeProfile.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
vlUploadMergeProfile.labelAttr = 'name';
vlUploadMergeProfile.searchAttr = 'name';
vlUploadMergeProfile.startup();
- vlUploadMergeProfile2.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(profiles)});
+ vlUploadMergeProfile2.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
vlUploadMergeProfile2.labelAttr = 'name';
vlUploadMergeProfile2.searchAttr = 'name';
vlUploadMergeProfile2.startup();
}
);
+ new openils.PermaCrud().search('vms',
+ {owner: owner.map(function(org) { return org.id(); })},
+ { async: true,
+ oncomplete: function(r) {
+ var sets = openils.Util.readResponse(r);
+ dojo.forEach(sets,
+ function(set) {
+ if(!matchSets[set.mtype()])
+ matchSets[set.mtype()] = [];
+ matchSets[set.mtype()].push(set);
+ }
+ );
+ checkInitDone();
+ }
+ }
+ );
+
vlAttrEditorInit();
+ vlExportInit();
}
openils.Util.removeCSSClass(dojo.byId('vl-menu-queue-select'), 'toolbar_selected');
openils.Util.removeCSSClass(dojo.byId('vl-menu-attr-editor'), 'toolbar_selected');
openils.Util.removeCSSClass(dojo.byId('vl-menu-profile-editor'), 'toolbar_selected');
+ openils.Util.removeCSSClass(dojo.byId('vl-menu-match-set-editor'), 'toolbar_selected');
+
+ if(dojo.byId('vl-match-set-iframe'))
+ dojo.byId('vl-match-set-editor-div').removeChild(dojo.byId('vl-match-set-iframe'));
switch(id) {
case 'vl-marc-export-div':
case 'vl-item-attr-editor-div':
openils.Util.addCSSClass(dojo.byId('vl-menu-import-item-attr-editor'), 'toolbar_selected');
break;
+ case 'vl-match-set-editor-div':
+ openils.Util.addCSSClass(dojo.byId('vl-menu-match-set-editor'), 'toolbar_selected');
+ break;
}
}
/**
* Creates a new vandelay queue
*/
-function createQueue(queueName, type, onload, importDefId) {
+function createQueue(queueName, type, onload, importDefId, matchSet) {
var name = (type=='bib') ? 'bib' : 'authority';
var method = 'open-ils.vandelay.'+ name +'_queue.create'
fieldmapper.standardRequest(
['open-ils.vandelay', method],
{ async: true,
- params: [authtoken, queueName, null, name, importDefId],
+ params: [authtoken, queueName, null, name, matchSet, importDefId],
oncomplete : function(r) {
var queue = r.recv().content();
if(e = openils.Event.parse(queue))
);
}
-function retrieveQueuedRecords(type, queueId, onload) {
+function vlExportInit() {
+
+ // queue export
+ var qsel = dojo.byId('vl-queue-export-options');
+ qsel.onchange = function(newVal) {
+ var value = qsel.options[qsel.selectedIndex].value;
+ qsel.selectedIndex = 0;
+ if(!value) return;
+ if(!confirm('Export as "' + value + '"?')) return; // TODO: i18n
+ retrieveQueuedRecords(
+ currentType,
+ currentQueueId,
+ function(r) {
+ exportHandler(value, r);
+ displayGlobalDiv('vl-queue-div');
+ },
+ value
+ );
+ }
+
+ // item export
+ var isel = dojo.byId('vl-item-export-options');
+ isel.onchange = function(newVal) {
+ var value = isel.options[isel.selectedIndex].value;
+ isel.selectedIndex = 0;
+ if(!value) return;
+ if(!confirm('Export as "' + value + '"?')) return; // TODO: i18n
+
+ var method = 'open-ils.vandelay.import_item.queue.export.' + value;
+
+ fieldmapper.standardRequest(
+ ['open-ils.vandelay', method],
+ {
+ params : [
+ authtoken,
+ currentQueueId,
+ {with_import_error: (vlImportItemsShowErrors.checked) ? 1 : null}
+ ],
+ async : true,
+ oncomplete : function(r) {exportHandler(value, r)}
+ }
+ );
+ }
+}
+
+function exportHandler(type, response) {
+ try {
+ var content = openils.Util.readResponse(response);
+ if (type=='email') {
+ if (content) { throw(content); }
+ return;
+ }
+ content = content[0].template_output().data();
+ switch(type) {
+ case 'print':
+ openils.Util.printHtmlString(content);
+ break;
+ case 'csv':
+ //content = content.replace(/\\t/g,'\t'); // if we really wanted to do .tsv instead
+ openils.XUL.contentToFileSaveDialog(content);
+ break;
+ default:
+ alert('response = ' + response + '\tcontent:\n' + content);
+ }
+ } catch(E) {
+ alert('Error exporting data: ' + E);
+ }
+}
+
+function retrieveQueuedRecords(type, queueId, onload, doExport) {
displayGlobalDiv('vl-generic-progress');
queuedRecords = [];
queuedRecordsMap = {};
currentOverlayRecordsMap = {};
currentOverlayRecordsMapGid = {};
selectableGridRecords = {};
- //resetVlQueueGridLayout();
if(!type) type = currentType;
if(!queueId) queueId = currentQueueId;
if(!onload) onload = handleRetrieveRecords;
- var method = 'open-ils.vandelay.'+type+'_queue.records.retrieve.atomic';
+ var method = 'open-ils.vandelay.'+type+'_queue.records.retrieve';
+
+ if(doExport) method += '.export.' + doExport;
if(vlQueueGridShowMatches.checked)
method = method.replace('records', 'records.matches');
+ method += '.atomic';
+
var sel = dojo.byId('vl-queue-display-limit-selector');
var limit = parseInt(sel.options[sel.selectedIndex].value);
var offset = limit * parseInt(vlQueueDisplayPage.attr('value')-1);
- var params = [authtoken, queueId, {clear_marc: 1, offset: offset, limit: limit}];
+ var params = [authtoken, queueId, {clear_marc: 1, offset: offset, limit: limit, flesh_import_items:1}];
if(vlQueueGridShowNonImport.checked)
params[2].non_imported = 1;
+ if(vlQueueGridShowImportErrors.checked)
+ params[2].with_import_error = 1;
+
fieldmapper.standardRequest(
['open-ils.vandelay', method],
{ async: true,
params: params,
- /*
- onresponse: function(r) {
- console.log("ONREPONSE");
- var rec = r.recv().content();
- if(e = openils.Event.parse(rec))
- return alert(e);
- console.log("got record " + rec.id());
- queuedRecords.push(rec);
- queuedRecordsMap[rec.id()] = rec;
- },
- */
oncomplete: function(r){
+ if(doExport) return onload(r);
var recs = r.recv().content();
if(e = openils.Event.parse(recs[0]))
return alert(e);
function vlLoadMatchUI(recId) {
displayGlobalDiv('vl-generic-progress');
- var matches = queuedRecordsMap[recId].matches();
+ var queuedRec = queuedRecordsMap[recId];
+ var matches = queuedRec.matches();
var records = [];
currentImportRecId = recId;
for(var i = 0; i < matches.length; i++)
var params = [records];
if(currentType == 'auth') {
retrieve = ['open-ils.cat', 'open-ils.cat.authority.record.retrieve'];
- parmas = [authtoken, records, {clear_marc:1}];
+ params = [authtoken, records, {clear_marc:1}];
}
fieldmapper.standardRequest(
// build the data store of records with match information
var dataStore = bre.toStoreData(recs, null,
- {virtualFields:['dest_matchpoint', 'src_matchpoint', '_id']});
+ {virtualFields:['_id', 'match_score', 'match_quality', 'rec_quality']});
dataStore.identifier = '_id';
var matchSeenMap = {};
for(var j = 0; j < matches.length; j++) {
var match = matches[j];
if(match.eg_record() == item.id && !matchSeenMap[match.id()]) {
- item.dest_matchpoint = match.field_type();
- var attr = getRecAttrFromMatch(queuedRecordsMap[recId], match);
- item.src_matchpoint = getRecAttrDefFromAttr(attr, currentType).code();
+ if(match.match_score)
+ item.match_score = match.match_score();
+ item.match_quality = match.quality();
+ item.rec_quality = queuedRec.quality();
matchSeenMap[match.id()] = 1;
break;
}
}
*/
+/*
function getRecAttrFromMatch(rec, match) {
for(var i = 0; i < rec.attributes().length; i++) {
var attr = rec.attributes()[i];
return attr;
}
}
+*/
function getRecAttrDefFromAttr(attr, type) {
var defs = (type == 'bib') ? bibAttrDefs : authAttrDefs;
return '<a href="javascript:void(0);" onclick="vlLoadMatchUI(' + id + ');">' + this.name + '</a>';
}
+function vlGetViewErrors(rowIdx, item) {
+ if(item) {
+ var id = this.grid.store.getValue(item, 'id');
+ var rec = queuedRecordsMap[id];
+ // id:rec_error:item_import_error_count
+ return id + ':' +
+ (rec.import_error() ? 1 : '') + ':' +
+ rec.import_items().filter(function(i) {return i.import_error()}).length;
+ }
+ return -1
+}
+
+function vlFormatViewErrors(chunk) {
+ if(chunk == -1) return '';
+ var id = chunk.split(':')[0];
+ var rec = chunk.split(':')[1];
+ var count = chunk.split(':')[2];
+ var links = '';
+ if(rec)
+ links += '<a href="javascript:void(0);" onclick="vlLoadErrorUI(' + id + ');">Record</a><br/>'; // TODO I18N
+ if(Number(count))
+ links += '<a href="javascript:void(0);" onclick="vlLoadErrorUI(' + id + ');">Items ('+count+')</a>'; // TODO I18N
+ return links;
+}
+
+//var vlItemErrorColumnPicker;
+function vlLoadErrorUI(id) {
+
+ displayGlobalDiv('vl-import-error-div');
+ openils.Util.hide('vl-import-error-grid-all');
+ openils.Util.show('vl-import-error-record');
+
+ var rec = queuedRecordsMap[id];
+
+ dojo.byId('vl-error-id').innerHTML = rec.id();
+ dojo.forEach( // TODO sane authority rec. fields
+ ['title', 'author', 'isbn', 'issn', 'upc'],
+ function(field) {
+ var attr = getRecAttrFromCode(rec, field);
+ var eid = 'vl-error-' + field;
+ if(attr) {
+ openils.Util.show(dojo.byId(eid).parentNode, 'table-row');
+ dojo.byId(eid).innerHTML = attr.attr_value();
+ } else {
+ openils.Util.hide(dojo.byId(eid).parentNode);
+ }
+ }
+ );
+ var iediv = dojo.byId('vl-error-import-error');
+ var eddiv = dojo.byId('vl-error-error-detail');
+ if(rec.import_error()) {
+ openils.Util.show(iediv.parentNode, 'table-row');
+ openils.Util.show(eddiv.parentNode, 'table-row');
+ iediv.innerHTML = rec.import_error();
+ eddiv.innerHTML = rec.error_detail();
+ } else {
+ openils.Util.hide(iediv.parentNode);
+ openils.Util.hide(eddiv.parentNode);
+ }
+
+ var errorItems = rec.import_items().filter(function(i) {return i.import_error()});
+ if(errorItems.length) {
+ openils.Util.show('vl-import-error-grid-some');
+ storeData = vqbr.toStoreData(errorItems);
+ var store = new dojo.data.ItemFileReadStore({data:storeData});
+ vlImportErrorGrid.setStore(store);
+ vlImportErrorGrid.update();
+ } else {
+ openils.Util.hide('vl-import-error-grid-some');
+ }
+}
+
+function vlLoadErrorUIAll() {
+
+ displayGlobalDiv('vl-import-error-div');
+ openils.Util.hide('vl-import-error-grid-some');
+ openils.Util.hide('vl-import-error-record');
+ openils.Util.show('vl-import-error-grid-all');
+ vlAllImportErrorGrid.resetStore();
+
+ vlImportErrorGrid.displayOffset = 0;
+
+ vlAllImportErrorGrid.dataLoader = function() {
+
+ vlAllImportErrorGrid.showLoadProgressIndicator();
+
+ fieldmapper.standardRequest(
+ ['open-ils.vandelay', 'open-ils.vandelay.import_item.queue.retrieve'],
+ {
+ async : true,
+ params : [
+ authtoken, currentQueueId, {
+ with_import_error: (vlImportItemsShowErrors.checked) ? 1 : null,
+ offset : vlAllImportErrorGrid.displayOffset,
+ limit : vlAllImportErrorGrid.displayLimit
+ }
+ ],
+ onresponse : function(r) {
+ var item = openils.Util.readResponse(r);
+ if(!item) return;
+ vlAllImportErrorGrid.store.newItem(vii.toStoreItem(item));
+ },
+ oncomplete : function() {
+ vlAllImportErrorGrid.hideLoadProgressIndicator();
+ }
+ }
+ );
+ };
+
+ vlAllImportErrorGrid.dataLoader();
+}
+
+function vlGetOrg(rowIdx, item) {
+ if(!item) return '';
+ var value = this.grid.store.getValue(item, this.field);
+ if(value) return fieldmapper.aou.findOrgUnit(value).shortname();
+ return '';
+}
+
function vlFormatViewMatchMARC(id) {
return '<a href="javascript:void(0);" onclick="vlLoadMARCHtml(' + id + ', true, '+
'function(){displayGlobalDiv(\'vl-match-div\');});">' + this.name + '</a>';
dojo.byId('vl-queue-summary-name').innerHTML = summary.queue.name();
dojo.byId('vl-queue-summary-total-count').innerHTML = summary.total +'';
dojo.byId('vl-queue-summary-import-count').innerHTML = summary.imported + '';
+ dojo.byId('vl-queue-summary-import-item-count').innerHTML = summary.total_items + '';
+ dojo.byId('vl-queue-summary-rec-error-count').innerHTML = summary.rec_import_errors + '';
+ dojo.byId('vl-queue-summary-item-error-count').innerHTML = summary.item_import_errors + '';
}
);
}
queueItemsImportDialog.hide();
// hack to set the widgets the import funcs will be looking at. Reset them below.
- vlUploadQueueAutoImport.attr('value', vlUploadQueueAutoImport2.attr('value'));
+ vlUploadQueueImportNoMatch.attr('value', vlUploadQueueImportNoMatch2.attr('value'));
vlUploadQueueAutoOverlayExact.attr('value', vlUploadQueueAutoOverlayExact2.attr('value'));
vlUploadQueueAutoOverlay1Match.attr('value', vlUploadQueueAutoOverlay1Match2.attr('value'));
vlUploadMergeProfile.attr('value', vlUploadMergeProfile2.attr('value'));
+ vlUploadQueueAutoOverlayBestMatch.attr('value', vlUploadQueueAutoOverlayBestMatch2.attr('value'));
+ vlUploadQueueAutoOverlayBestMatchRatio.attr('value', vlUploadQueueAutoOverlayBestMatchRatio2.attr('value'));
if(action == 'import') {
vlImportSelectedRecords();
}
// reset the widgets to prevent accidental future actions
- vlUploadQueueAutoImport.attr('value', false);
- vlUploadQueueAutoImport2.attr('value', false);
+ vlUploadQueueImportNoMatch.attr('value', false);
+ vlUploadQueueImportNoMatch2.attr('value', false);
vlUploadQueueAutoOverlayExact.attr('value', false);
vlUploadQueueAutoOverlayExact2.attr('value', false);
vlUploadQueueAutoOverlay1Match.attr('value', false);
vlUploadQueueAutoOverlay1Match2.attr('value', false);
vlUploadMergeProfile.attr('value', '');
vlUploadMergeProfile2.attr('value', '');
+ vlUploadQueueAutoOverlayBestMatch.attr('value', false);
+ vlUploadQueueAutoOverlayBestMatch2.attr('value', false);
+ vlUploadQueueAutoOverlayBestMatchRatio.attr('value', '0.0');
+ vlUploadQueueAutoOverlayBestMatchRatio2.attr('value', '0.0');
}
);
}
+/* import user-selected records */
function vlImportSelectedRecords() {
- displayGlobalDiv('vl-generic-progress-with-total');
var records = [];
for(var id in selectableGridRecords) {
}
}
+ vlImportRecordQueue(
+ currentType,
+ currentQueueId,
+ records,
+ function(){
+ retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
+ }
+ );
+}
+
+/* import all (non-imported) queue records */
+function vlImportAllRecords() {
+ vlImportRecordQueue(
+ currentType,
+ currentQueueId,
+ null,
+ function(){
+ retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
+ }
+ );
+}
+
+/* if recList has values, import only those records */
+function vlImportRecordQueue(type, queueId, recList, onload) {
+ displayGlobalDiv('vl-generic-progress-with-total');
+
+ /* set up options */
+ var mergeOpt = false;
var options = {overlay_map : currentOverlayRecordsMap};
+ if(vlUploadQueueImportNoMatch.checked) {
+ options.import_no_match = true;
+ vlUploadQueueImportNoMatch.checked = false;
+ }
+
if(vlUploadQueueAutoOverlayExact.checked) {
options.auto_overlay_exact = true;
vlUploadQueueAutoOverlayExact.checked = false;
+ mergeOpt = true;
+ }
+
+ if(vlUploadQueueAutoOverlayBestMatch.checked) {
+ options.auto_overlay_best_match = true;
+ vlUploadQueueAutoOverlayBestMatch.checked = false;
+ options.match_quality_ratio = vlUploadQueueAutoOverlayBestMatchRatio.attr('value');
+ mergeOpt = true;
}
if(vlUploadQueueAutoOverlay1Match.checked) {
options.auto_overlay_1match = true;
vlUploadQueueAutoOverlay1Match.checked = false;
+ mergeOpt = true;
}
-
+
var profile = vlUploadMergeProfile.attr('value');
if(profile != null && profile != '') {
options.merge_profile = profile;
}
- fieldmapper.standardRequest(
- ['open-ils.vandelay', 'open-ils.vandelay.'+currentType+'_record.list.import'],
- { async: true,
- params: [authtoken, records, options],
- onresponse: function(r) {
- var resp = r.recv().content();
- if(e = openils.Event.parse(resp))
- return alert(e);
- if(resp.complete) {
- return retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
- } else {
- vlControlledProgressBar.update({maximum:resp.total, progress:resp.progress});
- }
- },
- }
- );
-}
-
-function vlImportAllRecords() {
- vlImportRecordQueue(currentType, currentQueueId, false,
- function(){displayGlobalDiv('vl-queue-div');});
-}
+ /* determine which method we're calling */
-function vlImportRecordQueue(type, queueId, noMatchOnly, onload) {
- displayGlobalDiv('vl-generic-progress-with-total');
var method = 'open-ils.vandelay.bib_queue.import';
- if(noMatchOnly)
- method = method.replace('import', 'nomatch.import');
if(type == 'auth')
method = method.replace('bib', 'auth');
- var options = {};
- if(vlUploadQueueAutoOverlayExact.checked) {
- options.auto_overlay_exact = true;
- vlUploadQueueAutoOverlayExact.checked = false;
+ if(!mergeOpt) {
+ // in the interest of speed, if no merge options are
+ // chosen, tell the back-end code to only process records
+ // that have no matches
+ method = method.replace(/\.import/, '.nomatch.import');
}
- if(vlUploadQueueAutoOverlay1Match.checked) {
- options.auto_overlay_1match = true;
- vlUploadQueueAutoOverlay1Match.checked = false;
- }
-
- var profile = vlUploadMergeProfile.attr('value');
- if(profile != null && profile != '') {
- options.merge_profile = profile;
+ var params = [authtoken, queueId, options];
+ if(recList) {
+ method = 'open-ils.vandelay.'+currentType+'_record.list.import';
+ params[1] = recList;
}
fieldmapper.standardRequest(
['open-ils.vandelay', method],
{ async: true,
- params: [authtoken, queueId, options],
+ params: params,
onresponse: function(r) {
var resp = r.recv().content();
if(e = openils.Event.parse(resp))
currentType = dijit.byId('vl-record-type').getValue();
var handleProcessSpool = function() {
- if(vlUploadQueueAutoImport.checked || vlUploadQueueAutoOverlayExact.checked || vlUploadQueueAutoOverlay1Match.checked) {
- var noMatchOnly = !vlUploadQueueAutoOverlayExact.checked && !vlUploadQueueAutoOverlay1Match.checked;
- vlImportRecordQueue(
- currentType,
- currentQueueId,
- noMatchOnly,
- function() {
- retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
- }
- );
+ if(
+ vlUploadQueueImportNoMatch.checked ||
+ vlUploadQueueAutoOverlayExact.checked ||
+ vlUploadQueueAutoOverlay1Match.checked ||
+ vlUploadQueueAutoOverlayBestMatch.checked ) {
+
+ vlImportRecordQueue(
+ currentType,
+ currentQueueId,
+ null,
+ function() {
+ retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
+ }
+ );
} else {
retrieveQueuedRecords(currentType, currentQueueId, handleRetrieveRecords);
}
currentQueueId = vlUploadQueueSelector.getValue();
uploadMARC(handleUploadMARC);
} else {
- createQueue(queueName, currentType, handleCreateQueue, vlUploadQueueHoldingsImportProfile.attr('value'));
+ createQueue(queueName, currentType, handleCreateQueue,
+ vlUploadQueueHoldingsImportProfile.attr('value'),
+ vlUploadQueueMatchSet.attr('value')
+ );
}
}
selector.setDisplayedValue('');
if(data[0])
selector.setValue(data[0].id());
+
+ var qInput = dijit.byId('vl-queue-name');
+
+ var selChange = function(val) {
+ console.log('selector onchange');
+ // user selected a queue from the selector; clear the input and
+ // set the item import profile already defined for the queue
+ var queue = allUserBibQueues.filter(function(q) { return (q.id() == val) })[0];
+ if(val) {
+ vlUploadQueueHoldingsImportProfile.attr('value', queue.item_attr_def() || '');
+ vlUploadQueueHoldingsImportProfile.attr('disabled', true);
+ vlUploadQueueMatchSet.attr('value', queue.match_set() || '');
+ vlUploadQueueMatchSet.attr('disabled', true);
+ } else {
+ vlUploadQueueHoldingsImportProfile.attr('value', '');
+ vlUploadQueueHoldingsImportProfile.attr('disabled', false);
+ vlUploadQueueMatchSet.attr('value', '');
+ vlUploadQueueMatchSet.attr('disabled', false);
+ }
+ dojo.disconnect(qInput._onchange);
+ qInput.attr('value', '');
+ qInput._onchange = dojo.connect(qInput, 'onChange', inputChange);
+ }
+
+ var inputChange = function(val) {
+ console.log('qinput onchange');
+ // user entered a new queue name. clear the selector
+ vlUploadQueueHoldingsImportProfile.attr('value', '');
+ vlUploadQueueHoldingsImportProfile.attr('disabled', false);
+ vlUploadQueueMatchSet.attr('value', '');
+ vlUploadQueueMatchSet.attr('disabled', false);
+ dojo.disconnect(selector._onchange);
+ selector.attr('value', '');
+ selector._onchange = dojo.connect(selector, 'onChange', selChange);
+ }
+
+ selector._onchange = dojo.connect(selector, 'onChange', selChange);
+ qInput._onchange = dojo.connect(qInput, 'onChange', inputChange);
+}
+
+function vlUpdateMatchSetSelector(type) {
+ type = (type.match(/bib/)) ? 'biblio' : 'authority';
+ vlUploadQueueMatchSet.store =
+ new dojo.data.ItemFileReadStore({data:vms.toStoreData(matchSets[type])});
}
function vlShowUploadForm() {
vlUploadSourceSelector.setValue(vlBibSources[0].id());
vlUploadQueueHoldingsImportProfile.store =
new dojo.data.ItemFileReadStore({data:viiad.toStoreData(importItemDefs)});
+ vlUpdateMatchSetSelector(vlUploadRecordType.getValue());
+
+ // use ratio from the merge profile if it's set
+ dojo.connect(
+ vlUploadMergeProfile,
+ 'onChange',
+ function(val) {
+ if(!val) return;
+ var profile = mergeProfiles.filter(function(p) { return (p.id() == val); })[0];
+ if(profile.lwm_ratio() != null)
+ vlUploadQueueAutoOverlayBestMatchRatio.attr('value', profile.lwm_ratio()+'');
+ }
+ );
+ dojo.connect(
+ vlUploadMergeProfile2,
+ 'onChange',
+ function(val) {
+ if(!val) return;
+ var profile = mergeProfiles.filter(function(p) { return (p.id() == val); })[0];
+ if(profile.lwm_ratio() != null)
+ vlUploadQueueAutoOverlayBestMatchRatio2.attr('value', profile.lwm_ratio()+'');
+ }
+ );
+
}
function vlShowQueueSelect() {
vlFleshQueueSelect(vlQueueSelectQueueList, vlQueueSelectType.getValue());
}
+function vlShowMatchSetEditor() {
+ displayGlobalDiv('vl-match-set-editor-div');
+ dojo.byId('vl-match-set-editor-div').appendChild(
+ dojo.create('iframe', {
+ id : 'vl-match-set-iframe',
+ src : oilsBasePath + '/eg/conify/global/vandelay/match_set',
+ style : 'width:100%; height:500px; border:none; margin:0px;'
+ })
+ );
+}
+
function vlFetchQueueFromForm() {
currentType = vlQueueSelectType.getValue();
currentQueueId = vlQueueSelectQueueList.getValue();
var parsed_xpath = xpathParser.parse(this.store.getValue(row, 'xpath'));
dijit.byId('attr-editor-tags').attr('value', parsed_xpath.tags);
dijit.byId('attr-editor-subfields').attr('value', parsed_xpath.subfields);
- dijit.byId('attr-editor-identifier').attr('value', this.store.getValue(row, 'ident'));
dijit.byId('attr-editor-xpath').attr('value', this.store.getValue(row, 'xpath'));
dijit.byId('attr-editor-remove').attr('value', this.store.getValue(row, 'remove'));
<!ENTITY staff.main.menu.admin.server_admin.conify.z3950_source.label "Z39.50 Servers">
<!ENTITY staff.main.menu.admin.server_admin.conify.circulation_modifier.label "Circulation Modifiers">
<!ENTITY staff.main.menu.admin.server_admin.conify.org_unit_setting_type "Organization Unit Setting Types">
+<!ENTITY staff.main.menu.admin.server_admin.conify.import_match_set "Import Match Sets">
<!ENTITY staff.main.menu.admin.server_admin.conify.usr_setting_type "User Setting Types">
<!ENTITY staff.main.menu.admin.server_admin.conify.config_hard_due_date "Hard Due Date Changes">
<!ENTITY staff.main.menu.admin.server_admin.conify.config_rule_circ_duration "Circulation Duration Rules">
<!ENTITY vandelay.add.existing.queue "or Add to an Existing Queue">
<!ENTITY vandelay.auth.attrs "Authority attributes">
<!ENTITY vandelay.auth.records "Authority Records">
-<!ENTITY vandelay.auto.import.noncolliding "Auto-Import Non-Colliding Records">
-<!ENTITY vandelay.auto.import.auto_overlay_exact "Auto Merge/Overlay Exact Matches">
-<!ENTITY vandelay.auto.import.auto_overlay_1match "Auto Merge/Overlay When Exactly 1 Match is Found">
+<!ENTITY vandelay.auto.import.noncolliding "Import Non-Colliding Records">
+<!ENTITY vandelay.auto.import.auto_overlay_exact "Merge/Overlay Exact Matches (901c)">
+<!ENTITY vandelay.auto.import.auto_overlay_1match "Merge/Overlay Single Matches">
+<!ENTITY vandelay.auto.import.auto_overlay_best "Merge/Overlay Best Match">
+<!ENTITY vandelay.auto.import.auto_overlay_best_ratio "Best Match Minimum Quality Ratio">
+<!ENTITY vandelay.auto.import.auto_overlay_best_ratio.desc "New Record Quaility / Quality of Best Match">
<!ENTITY vandelay.auto.import.merge_profile "Merge/Overlay Profile">
<!ENTITY vandelay.auto.width "Auto Width">
<!ENTITY vandelay.back.to.import.queue "Back To Import Queue">
<!ENTITY vandelay.dest.match.point "Destination Match Point">
<!ENTITY vandelay.display "Display">
<!ENTITY vandelay.done "Done">
-<!ENTITY vandelay.edit.attributes "Edit Attributes">
-<!ENTITY vandelay.edit.attrs "Edit Attributes">
-<!ENTITY vandelay.edit.profiles "Edit Merge / Overlay Profiles">
-<!ENTITY vandelay.edit.import_item_attrs "Edit Import Item Attributes">
+<!ENTITY vandelay.edit.attributes "Record Display Attributes">
+<!ENTITY vandelay.edit.attrs "Record Display Attributes">
+<!ENTITY vandelay.edit.profiles "Merge / Overlay Profiles">
+<!ENTITY vandelay.edit.match_set "Record Match Sets">
+<!ENTITY vandelay.edit.import_item_attrs "Import Item Attributes">
<!ENTITY vandelay.false "False">
<!ENTITY vandelay.file.to.upload "File to Upload:">
<!ENTITY vandelay.for.example "Example">
<!ENTITY vandelay.id "ID">
<!ENTITY vandelay.import.matches "Import Matches">
<!ENTITY vandelay.import.records "Import Records">
-<!ENTITY vandelay.import.selected "Import Selected">
-<!ENTITY vandelay.import.all "Import All">
+<!ENTITY vandelay.import.selected "Import Selected Records">
+<!ENTITY vandelay.import.all "Import All Records">
<!ENTITY vandelay.import.time "Import Time">
<!ENTITY vandelay.inspect.queue "Inspect Queue">
<!ENTITY vandelay.last.edit.date "Last Edit Date">
-<!ENTITY vandelay.limit.to.collision.matches "Limit to Collision Matches">
+<!ENTITY vandelay.limit.to.collision.matches "Limit to Records with Matches">
<!ENTITY vandelay.limit.to.non.imported "Limit to Non-Imported Records">
+<!ENTITY vandelay.limit.to.import_error "Limit to Records with Import Errors">
<!ENTITY vandelay.marc.file.upload "Evergreen MARC File Upload">
<!ENTITY vandelay.marc.record "MARC Record">
<!ENTITY vandelay.matches "Matches">
-<!ENTITY vandelay.next.page "Next Page »">
+<!ENTITY vandelay.next.page "Next »">
<!ENTITY vandelay.overlay.selected.record "Overlay selected record with imported record">
<!ENTITY vandelay.overlay.target "Overlay Target">
<!ENTITY vandelay.page "Page">
<!ENTITY vandelay.powered.by.evergreen "Powered by Evergreen!">
-<!ENTITY vandelay.prev.page "« Previous Page">
+<!ENTITY vandelay.prev.page "« Previous">
<!ENTITY vandelay.processing "Processing... ">
<!ENTITY vandelay.queue "Queue">
<!ENTITY vandelay.queue.type "Queue Type">
<!ENTITY vandelay.refresh "Refresh">
<!ENTITY vandelay.remove.advanced "Remove (advanced)">
<!ENTITY vandelay.remove "Remove">
-<!ENTITY vandelay.results.per.page "Results Per Page">
+<!ENTITY vandelay.results.per.page "Records Per Page">
<!ENTITY vandelay.retrieve.queue "Retrieve Queue">
<!ENTITY vandelay.return "Return">
<!ENTITY vandelay.select.cols "Select Columns">
<!ENTITY vandelay.export.field_no_hint "(starting from 0)">
<!ENTITY vandelay.export.bucket "Record Bucket ID">
<!ENTITY vandelay.select_actions "-- Actions --">
-<!ENTITY vandelay.queue.total "Total:">
-<!ENTITY vandelay.queue.imported "Imported:">
+<!ENTITY vandelay.queue.total "Records in Queue:">
+<!ENTITY vandelay.queue.imported "Records Imported:">
<!ENTITY vandelay.queue.column_picker.title "Column Picker">
<!ENTITY vandelay.import.bib_sources "Select a Record Source">
--- /dev/null
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = 'Vandelay Match Sets' %]
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel">
+ <div>[% ctx.page_title %]</div>
+ <div>
+ <button dojoType="dijit.form.Button"
+ onClick="vms_grid.showCreateDialog()">New Match Set</button>
+ <button dojoType="dijit.form.Button"
+ onClick="vms_grid.deleteSelected()">Delete Selected</button>
+ </div>
+ </div>
+ <div>
+ Show sets owned at or below:
+ <select dojoType="openils.widget.OrgUnitFilteringSelect"
+ jsId="context_org_selector"></select>
+ </div>
+ <table jsId="vms_grid"
+ dojoType="openils.widget.AutoGrid"
+ query="{id: '*'}"
+ defaultCellWidth="'16em'"
+ fmClass="vms"
+ fieldorder="['name', 'owner']"
+ suppressEditFields="['id']"
+ showPaginator="true"
+ editOnEnter="true">
+ <thead>
+ <tr>
+ <th field="name" get="field_plus_id" formatter="tree_editor_link"></th>
+ <th field="owner" get="openils.widget.AutoGrid.orgUnitGetter">
+ </th>
+ </tr>
+ </thead>
+ </table>
+</div>
+<div class="hidden">
+ <select dojoType="dijit.form.FilteringSelect" jsId="mtype_selector">
+ [%# for the origin of these hard coded options, see the definition
+ of vandelay.match_set.mtype in 012.schema.vandelay.sql %]
+ <option value="biblio">biblio</option>
+ <option value="authority">authority</option>
+ <!-- XXX: nah <option value="mfhd">mfhd</option> -->
+ </select>
+</div>
+
+<script type="text/javascript">
+ dojo.require("dijit.form.FilteringSelect");
+ dojo.require("openils.widget.AutoGrid");
+ dojo.require("openils.widget.OrgUnitFilteringSelect");
+
+ var context_org;
+
+ function load_grid(search) {
+ if (!search) search = {"id": {"!=": null}};
+
+ vms_grid.loadAll({"order_by": {"vms": "name"}}, search);
+ }
+
+ function field_plus_id(rowIndex, item) {
+ if (!item) return null;
+ var datum = {};
+ datum[this.field] = this.grid.store.getValue(item, this.field);
+ datum.id = this.grid.store.getValue(item, "id");
+ return datum;
+ }
+
+ function tree_editor_link(datum) {
+ if (!datum) return "";
+ return '<a href="[% ctx.base_path %]/eg/conify/global/vandelay/match_set_tree?match_set=' +
+ datum.id + '">' + datum.name + '</a>';
+ }
+
+ openils.Util.addOnLoad(
+ function() {
+ new openils.User().buildPermOrgSelector(
+ "ADMIN_IMPORT_MATCH_SET", context_org_selector,
+ null, function() {
+ context_org_selector.onChange = function() {
+ context_org = this.attr("value");
+ vms_grid.resetStore();
+ load_grid({
+ "owner": aou.descendantNodeList(context_org, true)
+ });
+ };
+ }
+ );
+
+ vms_grid.overrideEditWidgets.mtype = mtype_selector;
+ vms_grid.overrideEditWidgets.mtype.shove = {"create": "biblio"};
+ load_grid();
+ }
+ );
+</script>
+[% END %]
--- /dev/null
+[% WRAPPER 'default/base.tt2' %]
+[% ctx.page_title = 'Vandelay Match Set Editor' %]
+<style type="text/css">
+ h1 { margin: 1ex 0; }
+ .outer { clear: both; margin-bottom: 1ex; }
+ .space-me input { margin: 0 1em; }
+ button { margin: 0 0.5em; }
+ input[type=submit] { padding: 0 0.5em; }
+ #tree-here { margin-bottom: 1.5em; }
+ #vms-table { padding-bottom: 2ex; }
+ #vms-table th { text-align: right; }
+ #vms-table td { padding-left: 1em; }
+ #src-pane { float: left; width: 49%; }
+ #tree-pane { float: right; width: 50%; }
+ #submit-row { clear: both; text-align: center; padding-top: 1.5ex; }
+ #submit-row hr { margin: 1.5ex 0; }
+ #expr-preview-row { margin: 2ex 0; }
+ #expr-preview {
+ font-family: monospace;
+ font-size: 125%;
+ font-weight: bold;
+ background-color: #000066;
+ color: white;
+ }
+ .node-editor { margin-bottom: 1.5ex; }
+ .node-editor td { padding: 0.5ex; }
+ li { background-color: #ddd; }
+ .replace-mode { background-color: #990; color: #fff; }
+</style>
+<h1>[% ctx.page_title %]</h1>
+<table id="vms-table">
+ <tbody>
+ <tr>
+ <th>Match set name:</th>
+ <td><strong id="vms-name"></strong></td>
+ </tr>
+ <tr>
+ <th>Owning Library:</th>
+ <td id="vms-owner"></td>
+ </tr>
+ <tr>
+ <th>Type:</th>
+ <td id="vms-mtype"></td>
+ </tr>
+ </tbody>
+</table>
+<hr />
+<!-- XXX TODO
+ give some indication of which match set we're editing the tree for
+ -->
+<table class="hidden">
+ <tr quality-controls="1">
+ <td>
+ <label for="value-input">Value:</label>
+ </td>
+ <td>
+ <input id="value-input" type="text" fmfield="value" />
+ </td>
+ </tr>
+ <tr consistent-controls="1">
+ <td>
+ <label for="quality-input"
+ title="A relative number representing the impact of this expression on the quality of the overall record match"><!-- XXX tooltipize -->
+ Quality:
+ </label>
+ </td>
+ <td>
+ <input id="quality-input" type="text" value="1"
+ size="4" maxlength="3" fmfield="quality" />
+ </td>
+ </tr>
+ <tr point-controls="1">
+ <td>
+ <label for="negate-input">Negate?</label>
+ </td>
+ <td>
+ <input id="negate-input" type="checkbox" fmfield="negate" />
+ </td>
+ </tr>
+</table>
+<div class="outer">
+ <div id="expr-preview-row">
+ <em>Your Expression:</em>
+ <span id="expr-preview"></span>
+ </div>
+ <div id="vmsp-buttons">
+ Add new
+ <button onclick="node_editor.add('svf');">Record Attribute</button>
+ <button onclick="node_editor.add('tag');">MARC Tag and Subfield</button>
+ <button onclick="node_editor.add('bool_op');">Boolean Operator</button>
+ </div>
+</div>
+<div class="outer" style="margin-top: 2ex;">
+ <div id="src-pane">
+ <div><big>Working Match Point</big></div>
+ <div>
+ <form id="node-editor-container" onsubmit="return false;"></form>
+ </div>
+ <ul id="src-here"></ul>
+ </div>
+
+ <div id="tree-pane">
+ <div><big>Your Expression</big></div>
+ <div id="tree-here"></div>
+ <div>
+ <button id="deleter" onclick="delete_selected_in_tree()">
+ Delete Selected Node
+ </button>
+ <button id="replacer" onclick="replace_mode()"></button>
+ </div>
+ </div>
+</div>
+<div id="submit-row">
+ <button onclick="save_tree()">Save Changes To Expression</button>
+</div>
+<hr />
+<div id="quality-editor-wrapper">
+ <div>
+ <div style="float: left; width: 50%;">
+ <big>Quality Metrics for this Match Set</big>
+ </div>
+ <div style="float: right; width: 49%; text-align: right;">
+ <button onclick="qnode_editor.add('svf')">
+ Record Attribute
+ </button>
+ <button onclick="qnode_editor.add('tag')">
+ MARC Tag and Subfield
+ </button>
+ <button onclick="vmsq_grid.deleteSelected()">
+ Delete Selected Metrics
+ </button>
+ </div>
+ </div>
+ <br style="clear: both;" />
+ <table id="qnode-editor-container"></table>
+ <table jsId="vmsq_grid"
+ dojoType="openils.widget.AutoGrid"
+ query="{id: '*'}"
+ defaultCellWidth="'17%'"
+ fmClass="vmsq"
+ fieldOrder="['quality','svf','tag','subfield','value']"
+ suppressFields="['match_set']"
+ showPaginator="true"
+ editOnEnter="false">
+ </table>
+</div>
+<div jsId="progress_dialog" dojoType="openils.widget.ProgressDialog"></div>
+<script type="text/javascript"
+ src="[% ctx.media_prefix %]/js/ui/default/conify/global/vandelay/match_set.js"></script>
+[% END %]
</td>
</tr>
<tr>
- <td><label for="ident">&vandelay.id.field;: </label></td>
- <td>
- <select dojoType="dijit.form.FilteringSelect" name="ident" id="attr-editor-identifier">
- <option value='f' selected='selected'>&vandelay.false;</option>
- <option value='t'>&vandelay.true;</option>
- </select>
- </td>
- </tr>
- <tr>
<td><label for="attr-editor-xpath">&vandelay.xpath.advanced;: </label></td>
<td><input dojoType="dijit.form.TextBox" id="attr-editor-xpath" name="xpath"></input></td>
<th field='description' width='auto'>&vandelay.descrip;</th>
<th field='tag' get='attrGridGetTag'>&vandelay.tag;</th>
<th field='subfield' get='attrGridGetSubfield'>&vandelay.subfield;</th>
- <th field='ident'>&vandelay.identifier;</th>
<th field='xpath' width='auto'>&vandelay.xpath;</th>
<th field='remove' width='auto'>&vandelay.remove;</th>
</tr>
--- /dev/null
+
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client'>
+ <button dojoType='dijit.form.Button'
+ onclick="displayGlobalDiv('vl-queue-div');">↖ &vandelay.back.to.import.queue;</button>
+</div>
+
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client'>
+ <div id='vl-import-error-record' class='hidden'>
+ <h1>Import Errors</h1><br/>
+ <table>
+ <tbody>
+ <tr><td>ID</td><td id='vl-error-id'/></tr>
+ <tr><td>Import Error</td><td id='vl-error-import-error'/></tr>
+ <tr><td>Error Detail</td><td id='vl-error-error-detail'/></tr>
+ <tr><td>Title</td><td id='vl-error-title'/></tr>
+ <tr><td>Author</td><td id='vl-error-author'/></tr>
+ <tr><td>ISBN</td><td id='vl-error-isbn'/></tr>
+ <tr><td>ISSN</td><td id='vl-error-issn'/></tr>
+ <tr><td>UPC</td><td id='vl-error-upc'/></tr>
+ </tbody>
+ </table>
+ </div>
+</div>
+
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client'>
+ <div class='hidden' id='vl-import-error-grid-some'>
+ <table jsId="vlImportErrorGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['barcode', 'call_number', 'owning_lib', 'import_error', 'error_detail']"
+ query="{id: '*'}"
+ hidePaginator='true'
+ showColumnPicker='true'
+ columnPickerPrefix='"vandelay.item.import_error"'
+ fmClass='vii'>
+ <thead>
+ <tr>
+ <th field='owning_lib' get='vlGetOrg'/>
+ <th field='circ_lib' get='vlGetOrg'/>
+ </tr>
+ </thead>
+ </table>
+ </div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client'>
+ <div class='hidden' id='vl-import-error-grid-all'>
+ <h1>Import Items</h1><br/>
+ <input dojoType='dijit.form.CheckBox' jsId='vlImportItemsShowErrors' onchange='vlLoadErrorUIAll();'/>
+ <span>Limit to Import Failures</span>
+ <table width='100%'><tr><td width='100%' align='right'>
+ <select id='vl-item-export-options' style='margin-right: 10px;'>
+ <!-- TODO I18N -->
+ <option value=''>Export Items As...</option>
+ <option value='print'>Print</option>
+ <option value='csv'>CSV</option>
+ <option value='email'>Email</option>
+ </select>
+ </td></tr></table>
+ <table jsId="vlAllImportErrorGrid"
+ dojoType="openils.widget.AutoGrid"
+ autoHeight='true'
+ fieldOrder="['barcode', 'call_number', 'owning_lib', 'import_error', 'error_detail']"
+ query="{id: '*'}"
+ showPaginator='true'
+ showColumnPicker='true'
+ columnPickerPrefix='"vandelay.item.import_error"'
+ fmClass='vii'>
+ <thead>
+ <tr>
+ <th field='owning_lib' get='vlGetOrg'/>
+ <th field='circ_lib' get='vlGetOrg'/>
+ </tr>
+ </thead>
+ </table>
+ </div>
+</div>
+
name: '&vandelay.overlay.target;',
get: vlGetOverlayTargetSelector,
formatter : vlFormatOverlayTargetSelector,
- /*
- value: '<input type="checkbox" name="vl-overlay-target-RECID" '+
- 'onclick="vlHandleOverlayTargetSelected(ID, GRIDID);" gridid="GRIDID" match="ID"/>'
- */
},
- {name:'&vandelay.source.match.point;', field:'src_matchpoint'},
- {name:'&vandelay.dest.match.point;', field:'dest_matchpoint'},
{name: '&vandelay.id;', field:'id'},
{ name: '&vandelay.view.marc;',
get: vlGetViewMARC,
formatter : vlFormatViewMatchMARC
},
+ {name: 'Match Score', field:'match_score'},
+ {name: 'Queued Record Quality', field:'rec_quality'},
+ {name: 'Matched Record Quality', field:'match_quality'},
{name: '&vandelay.creator;', get: vlGetCreator},
{name: '&vandelay.create.date;', field:'create_date', get: vlGetDateTimeField},
{name: '&vandelay.last.edit.date;', field:'edit_date', get: vlGetDateTimeField},
</div>
<table jsId="pGrid"
dojoType="openils.widget.AutoGrid"
- fieldOrder="['name', 'owner', 'preserve_spec', 'replace_spec', 'add_spec', 'strip_spec']"
+ fieldOrder="['name', 'owner', 'preserve_spec', 'replace_spec', 'add_spec', 'strip_spec', 'lwm_ratio']"
query="{id: '*'}"
- defaultCellWidth='"auto"'
+ defaultCellWidth='"14%"'
fmClass='vmp'
showPaginator='true'
editOnEnter='true'>
-<div dojoType="dijit.layout.ContentPane" layoutAlign='client'>
- <table class='wide'>
- <tr>
- <td align='left'>
- <h1>&vandelay.record.queue; <span style='font-style:italic;' id='vl-queue-summary-name'/></h1><br/>
- </td>
- <td align='right'>
- &vandelay.queue.total; <span style='font-weight:bold;' id='vl-queue-summary-total-count'/>
- &vandelay.queue.imported; <span style='font-weight:bold;' id='vl-queue-summary-import-count'/>
- </td>
- </tr>
- </table>
-</div>
-
-<br/>
-
-<div jsId='queueItemsImportDialog' dojoType="dijit.Dialog" title="Import Items">
- <div dojoType="dijit.layout.ContentPane">
- <table class='form_table'>
- <tbody>
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client' style='margin-top:10px;'>
+ <fieldset id='vl-queue-filter-fieldset'>
+ <legend>Queue <span style='font-style:italic;' id='vl-queue-summary-name'/></legend>
+ <table width='100%'><tr>
+ <td> <!-- big left td -->
+ <table>
<tr>
- <td>&vandelay.auto.import.noncolliding;</td>
- <td colspan='4'>
- <input jsId='vlUploadQueueAutoImport2' dojoType='dijit.form.CheckBox'/>
- </td>
- </tr>
- <tr>
- <td>&vandelay.auto.import.auto_overlay_exact;</td>
- <td colspan='4'>
- <input jsId='vlUploadQueueAutoOverlayExact2' dojoType='dijit.form.CheckBox'/>
- </td>
- </tr>
- <tr>
- <td>&vandelay.auto.import.auto_overlay_1match;</td>
- <td colspan='4'>
- <input jsId='vlUploadQueueAutoOverlay1Match2' dojoType='dijit.form.CheckBox'/>
- </td>
- </tr>
- <tr>
- <td>&vandelay.auto.import.merge_profile;</td>
- <td colspan='4'>
- <div jsId='vlUploadMergeProfile2'
- dojoType='dijit.form.FilteringSelect' required='false' labelAttr='name' searchAttr='name'/>
+ <td valign='top'>
+ <table class='queue-nav-table'>
+ <thead><tr><th colspan='2' class='queue-nav-table-label'>Queue Actions</th></tr></thead>
+ <tbody>
+ <tr><td><a href='javascript:;' onclick='vlHandleQueueItemsAction("import")'>&vandelay.import.selected;</a></td></tr>
+ <tr><td><a href='javascript:;' onclick='vlHandleQueueItemsAction("import_all")'>&vandelay.import.all;</a></td></tr>
+ <tr><td><a href='javascript:;' onclick='vlLoadErrorUIAll();'>View Import Items</a></td></tr>
+ <tr><td><a href='javascript:;' onclick='
+ if(confirm("&vandelay.sure.to.delete.queue;")) {
+ vlDeleteQueue(currentType, currentQueueId,
+ function() { displayGlobalDiv("vl-marc-upload-div"); });}'>&vandelay.delete.queue;</a></td></tr>
+ </tbody>
+ </table>
</td>
- </tr>
- <tr>
- <td>
- <button dojoType='dijit.form.Button' jsId='queueItemsImportCancelButton'>Cancel</button>
+
+ <td valign='top'>
+ <table id='vl-queue-summary-table' class='queue-nav-table'>
+ <thead><tr><th colspan='2' class='queue-nav-table-label'>Queue Summary</th></tr></thead>
+ <tbody>
+ <tr><td>&vandelay.queue.total;</td><td> <span style='font-weight:bold;' id='vl-queue-summary-total-count'/></td></tr>
+ <tr><td>&vandelay.queue.imported;</td><td> <span style='font-weight:bold;' id='vl-queue-summary-import-count'/></td></tr>
+ <tr><td>Record Import Failures</td><td> <span style='font-weight:bold;' id='vl-queue-summary-rec-error-count'/></td></tr>
+ <tr><td>Items in Queue</td><td> <span style='font-weight:bold;' id='vl-queue-summary-import-item-count'/></td></tr>
+ <tr><td>Item Import Failures</td><td> <span style='font-weight:bold;' id='vl-queue-summary-item-error-count'/></td></tr>
+ </tbody>
+ </table>
</td>
- <td>
- <button dojoType='dijit.form.Button' jsId='queueItemsImportGoButton'>Import</button>
+
+ <td valign='top'> <!-- filters -->
+ <table id='vl-queue-filter-table' class='queue-nav-table'>
+ <thead><tr><th colspan='2' class='queue-nav-table-label'>Queue Filters</th></tr></thead>
+ <tbody>
+ <tr>
+ <td>&vandelay.limit.to.collision.matches;</td>
+ <td>
+ <input dojoType='dijit.form.CheckBox'
+ jsId='vlQueueGridShowMatches' onchange='retrieveQueuedRecords();'/>
+ </td>
+ </tr>
+ <tr>
+ <td>&vandelay.limit.to.non.imported;</td>
+ <td>
+ <input dojoType='dijit.form.CheckBox'
+ jsId='vlQueueGridShowNonImport' onchange='retrieveQueuedRecords();'/>
+ </td>
+ </tr>
+ <tr>
+ <td>&vandelay.limit.to.import_error;</td>
+ <td>
+ <input dojoType='dijit.form.CheckBox'
+ jsId='vlQueueGridShowImportErrors' onchange='retrieveQueuedRecords();'/>
+ </td>
+ </tr>
+ </tbody>
+ </table>
</td>
</tr>
- </tbody>
- </table>
- </div>
-</div>
+ </table>
+ </td>
-<!-- queue grid navigation row -->
-<div dojoType="dijit.layout.ContentPane" layoutAlign='client'>
-
- <table width='100%' style='margin-bottom:0px;'>
- <tr>
- <td align='left' valign='bottom'>
- <select id='vl-queue-actions-selector'>
- <option selected='selected' disabled='disabled' value='select-actions'>&vandelay.select_actions;</option>
- <option value='import'>&vandelay.import.selected;</option>
- <option value='import_all'>&vandelay.import.all;</option>
- <option value='delete_queue'>&vandelay.delete.queue;</option>
- </select>
- <script type="text/javascript">
- var sel = dojo.byId('vl-queue-actions-selector');
- sel.onchange = function(evt) {
- switch(openils.Util.selectorValue(evt.target)) {
- case 'import': vlHandleQueueItemsAction('import'); break;;
- case 'import_all': vlHandleQueueItemsAction('import_all'); break;;
- case 'delete_queue':
- if(confirm('&vandelay.sure.to.delete.queue;')) {
- vlDeleteQueue(currentType, currentQueueId,
- function() { displayGlobalDiv('vl-marc-upload-div'); });
- }
- }
- evt.target.selectedIndex = 0;
- }
- </script>
- </td>
- <td align='middle' valign='bottom'>
- <style type="text/css">.filter_span { padding-right: 5px; border-right: 2px solid #e8e1cf; } </style>
- <table><tr>
- <td>
- <span>&vandelay.limit.to.collision.matches;</span>
- <span class='filter_span'>
- <input dojoType='dijit.form.CheckBox' jsId='vlQueueGridShowMatches' onchange='retrieveQueuedRecords();'/>
- </span>
-
- <span>&vandelay.limit.to.non.imported;</span>
- <span class='filter_span'>
- <input dojoType='dijit.form.CheckBox' jsId='vlQueueGridShowNonImport'
- checked='checked' onchange='retrieveQueuedRecords();'/>
- </span>
-
- <span>&vandelay.results.per.page;</span>
- <span class='filter_span'>
+ <td align='right' valign='bottom'> <!-- big right td -->
+ <table id='vl-queue-paging-table' class='queue-nav-table'>
+ <tbody>
+ <tr><td valign='bottom' align='right'>
+ <select id='vl-queue-export-options' style='margin-right: 10px;'>
+ <!-- TODO I18N -->
+ <option value=''>Export Queue As...</option>
+ <option value='print'>Print</option>
+ <option value='csv'>CSV</option>
+ <option value='email'>Email</option>
+ </select>
+ <span style='padding-right:5px;'>&vandelay.results.per.page;</span>
+ <span class='queue-pager-span'>
<select jsId='vlQueueDisplayLimit' id='vl-queue-display-limit-selector'
value='10' onchange='retrieveQueuedRecords();'>
<option value='10'>10</option>
</select>
</span>
- <span style='padding-left:5px;'>&vandelay.page;</span>
- <input style='width:36px;' dojoType='dijit.form.TextBox' jsId='vlQueueDisplayPage' value='1'/>
- </td>
- </tr></table>
- </td>
- <td align='right' valign='bottom'>
- <span style='padding-right:4px;'>
- <a href='javascript:void(0);' onclick='vlQueueGridPrevPage();'>&vandelay.prev.page;</a>
- </span>
- <span style='padding-right:10px;'>
- <a href='javascript:void(0);' onclick='vlQueueGridNextPage();'>&vandelay.next.page;</a>
- </span>
- </td>
- </tr>
- </table>
-</div>
+ <span class='queue-pager-span'>
+ <span style='padding-left:5px;'>&vandelay.page;</span>
+ <input style='width:36px;' dojoType='dijit.form.TextBox' jsId='vlQueueDisplayPage' value='1'/>
+ </span>
+ <span style='padding-right:8px;'>
+ <a href='javascript:void(0);' onclick='vlQueueGridPrevPage();'>&vandelay.prev.page;</a>
+ </span>
+ <span style='padding-right:10px;'>
+ <a href='javascript:void(0);' onclick='vlQueueGridNextPage();'>&vandelay.next.page;</a>
+ </span>
+ </td></tr>
+ </tbody>
+ </table>
+ </td>
+ </tr></table>
+ </fieldset>
+</div>
<!-- Bib Queue Grid -->
<div class='' id='vl-bib-queue-grid-wrapper' dojoType='dijit.layout.ContentPane'>
field='+row_selector'
get='vlQueueGridDrawSelectBox'
formatter='vlQueueGridFormatSelectBox'
- styles='text-align: center;'
+ styles='text-align: center;width:30px;'
nonSelectable='true'>
<input id="vl-queue-grid-row-selector" type="checkbox" onclick="vlToggleQueueGridSelect();"></input>
</th>
styles='text-align: center;'
nonSelectable='true'>&vandelay.matches;</th>
<th
+ field='+get_errors'
+ get='vlGetViewErrors'
+ formatter='vlFormatViewErrors'
+ styles='text-align: center;'
+ nonSelectable='true'>Import Failures</th>
+ <th
field='import_time'
styles='text-align: center;'
get='vlGetDateTimeField'>&vandelay.import.time;</th>
styles='text-align: center;'
nonSelectable='true'>&vandelay.matches;</th>
<th
+ field='+get_errors'
+ get='vlGetViewErrors'
+ formatter='vlFormatViewErrors'
+ styles='text-align: center;'
+ nonSelectable='true'>Import Failures</th>
+ <th
field='import_time'
styles='text-align: center;'
get='vlGetDateTimeField'>&vandelay.import.time;</th>
<div/>
</div>
+<div jsId='queueItemsImportDialog' dojoType="dijit.Dialog" title="Import Items">
+ <div dojoType="dijit.layout.ContentPane">
+ <table class='form_table'>
+ <tbody>
+ <tr>
+ <td>&vandelay.auto.import.merge_profile;</td>
+ <td colspan='4'>
+ <div jsId='vlUploadMergeProfile2'
+ dojoType='dijit.form.FilteringSelect' required='false' labelAttr='name' searchAttr='name'/>
+ </td>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.noncolliding;</td>
+ <td colspan='4'>
+ <input jsId='vlUploadQueueImportNoMatch2' dojoType='dijit.form.CheckBox'/>
+ </td>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.auto_overlay_exact;</td>
+ <td colspan='4'>
+ <input jsId='vlUploadQueueAutoOverlayExact2' dojoType='dijit.form.CheckBox'/>
+ </td>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.auto_overlay_1match;</td>
+ <td colspan='4'>
+ <input jsId='vlUploadQueueAutoOverlay1Match2' dojoType='dijit.form.CheckBox'/>
+ </td>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.auto_overlay_best;</td>
+ <td colspan='4'><input jsId='vlUploadQueueAutoOverlayBestMatch2' dojoType='dijit.form.CheckBox'/></td>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.auto_overlay_best_ratio;</td>
+ <td colspan='4'>
+ <input style='width:3em' value='0.0' jsId='vlUploadQueueAutoOverlayBestMatchRatio2' dojoType='dijit.form.TextBox'/>
+ <span style='padding-left: 10px; font-size:90%'>(&vandelay.auto.import.auto_overlay_best_ratio.desc;)</span>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <button dojoType='dijit.form.Button' jsId='queueItemsImportCancelButton'>Cancel</button>
+ </td>
+ <td>
+ <button dojoType='dijit.form.Button' jsId='queueItemsImportGoButton'>Import</button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
+
+
onclick="vlShowAttrEditor();" showLabel="true">&vandelay.edit.attributes;</div>
<div dojoType="dijit.form.Button" iconClass="dijitEditorIcon dijitEditorIconCopy" id='vl-menu-profile-editor'
onclick="vlShowProfileEditor();" showLabel="true">&vandelay.edit.profiles;</div>
+ <div dojoType="dijit.form.Button" iconClass="dijitEditorIcon dijitEditorIconCopy" id='vl-menu-match-set-editor'
+ onclick="vlShowMatchSetEditor();" showLabel="true">&vandelay.edit.match_set;</div>
<div dojoType="dijit.form.Button" iconClass="dijitEditorIcon dijitEditorIconCopy" id='vl-menu-import-item-attr-editor'
onclick="vlShowImportItemAttrEditor();" showLabel="true">&vandelay.edit.import_item_attrs;</div>
</div>
</td>
</tr>
<tr>
- <td>&vandelay.auto.import.noncolliding;</td>
- <td colspan='4'>
- <input jsId='vlUploadQueueAutoImport' dojoType='dijit.form.CheckBox'/>
+ <td>Record Match Set</td>
+ <td>
+ <input jsId='vlUploadQueueMatchSet'
+ dojoType='dijit.form.FilteringSelect' labelAttr='name' searchAttr='name'/>
</td>
</tr>
<tr>
- <td>&vandelay.auto.import.auto_overlay_exact;</td>
- <td colspan='4'>
- <input jsId='vlUploadQueueAutoOverlayExact' dojoType='dijit.form.CheckBox'/>
+ <td>Holdings Import Profile</td>
+ <td>
+ <input jsId='vlUploadQueueHoldingsImportProfile'
+ dojoType='dijit.form.FilteringSelect' labelAttr='name' searchAttr='name'/>
</td>
</tr>
<tr>
- <td>&vandelay.auto.import.auto_overlay_1match;</td>
- <td colspan='4'>
- <input jsId='vlUploadQueueAutoOverlay1Match' dojoType='dijit.form.CheckBox'/>
+ <td>&vandelay.import.bib_sources;</td>
+ <td>
+ <select name='bib_source' jsId='vlUploadSourceSelector'
+ dojoType='dijit.form.FilteringSelect' labelAttr='source' searchAttr='source'/>
</td>
</tr>
+ <tr><td colspan='2' style='margin-top:10px;border-bottom:1px solid #888;border-top:1px solid #888'><b>Import Actions</b></td></tr>
<tr>
<td>&vandelay.auto.import.merge_profile;</td>
<td colspan='4'>
</td>
</tr>
<tr>
- <td>Holdings Import Profile</td>
- <td>
- <input jsId='vlUploadQueueHoldingsImportProfile'
- dojoType='dijit.form.FilteringSelect' labelAttr='name' searchAttr='name'/>
+ <td>&vandelay.auto.import.noncolliding;</td>
+ <td colspan='4'>
+ <input jsId='vlUploadQueueImportNoMatch' dojoType='dijit.form.CheckBox'/>
</td>
</tr>
<tr>
- <td>&vandelay.import.bib_sources;</td>
- <td>
- <select name='bib_source' jsId='vlUploadSourceSelector'
- dojoType='dijit.form.FilteringSelect' labelAttr='source' searchAttr='source'/>
+ <td>&vandelay.auto.import.auto_overlay_exact;</td>
+ <td colspan='4'>
+ <input jsId='vlUploadQueueAutoOverlayExact' dojoType='dijit.form.CheckBox'/>
</td>
</tr>
<tr>
- <td>
- <span id="vl-file-label">&vandelay.file.to.upload;</span>
+ <td>&vandelay.auto.import.auto_overlay_1match;</td>
+ <td colspan='4'>
+ <input jsId='vlUploadQueueAutoOverlay1Match' dojoType='dijit.form.CheckBox'/>
</td>
- <td id='vl-input-td' colspan='4'>
- <input size='48' type="file" name="marc_upload"/>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.auto_overlay_best;</td>
+ <td colspan='4'><input jsId='vlUploadQueueAutoOverlayBestMatch' dojoType='dijit.form.CheckBox'/></td>
+ </tr>
+ <tr>
+ <td>&vandelay.auto.import.auto_overlay_best_ratio;</td>
+ <td colspan='4'>
+ <input style='width:3em' value='0.0' jsId='vlUploadQueueAutoOverlayBestMatchRatio' dojoType='dijit.form.TextBox'/>
+ <span style='padding-left: 10px; font-size:90%'>(&vandelay.auto.import.auto_overlay_best_ratio.desc;)</span>
</td>
</tr>
+
+ <tr><td colspan='2' style='border-bottom:1px solid #888'></td></tr>
<tr>
- <td align='center' colspan='4'>
- <button dojoType="dijit.form.Button" onclick="batchUpload()">&vandelay.upload;</button>
+ <td colspan='5'>
+ <span id="vl-file-label">&vandelay.file.to.upload;</span>
+ <input size='48' type="file" name="marc_upload"/>
+ <span style='margin-left:10px;'><button dojoType="dijit.form.Button" onclick="batchUpload()">&vandelay.upload;</button></span>
</td>
</tr>
</table>
<div dojoType="dijit.layout.ContentPane" layoutAlign='client' id='vl-profile-editor-div' class='hidden content'>
[% INCLUDE 'default/vandelay/inc/profiles.tt2' %]
</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client' id='vl-match-set-editor-div' class='hidden content'>
+</div>
<div dojoType="dijit.layout.ContentPane" layoutAlign='client' id='vl-item-attr-editor-div' class='hidden content'>
[% INCLUDE 'default/vandelay/inc/item_attrs.tt2' %]
</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign='client' id='vl-import-error-div' class='hidden content'>
+ [% INCLUDE 'default/vandelay/inc/import_errors.tt2' %]
+</div>
[% END %]
['oncommand'],
function(event) { open_eg_web_page('conify/global/config/org_unit_setting_type', null, event); }
],
+ 'cmd_server_admin_import_match_set' : [
+ ['oncommand'],
+ function(event) { open_eg_web_page('conify/global/vandelay/match_set', null, event); }
+ ],
'cmd_server_admin_usr_setting_type' : [
['oncommand'],
function(event) { open_eg_web_page('conify/global/config/usr_setting_type', null, event); }
perm="ADMIN_GLOBAL_FLAG"
/>
<command id="cmd_server_admin_org_unit_setting_type" />
+ <command id="cmd_server_admin_import_match_set" />
<command id="cmd_server_admin_usr_setting_type" />
<command id="cmd_server_admin_config_hard_due_date"
perm="CREATE_CIRC_DURATION DELETE_CIRC_DURATION UPDATE_CIRC_DURATION"
<menuitem label="&staff.main.menu.admin.server_admin.conify.z3950_source.label;" command="cmd_server_admin_z39_source"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.circulation_modifier.label;" command="cmd_server_admin_circ_mod"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.global_flag.label;" command="cmd_server_admin_global_flag"/>
+ <menuitem label="&staff.main.menu.admin.server_admin.conify.import_match_set;" command="cmd_server_admin_import_match_set"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.org_unit_setting_type;" command="cmd_server_admin_org_unit_setting_type"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.usr_setting_type;" command="cmd_server_admin_usr_setting_type"/>
<menuitem label="&staff.main.menu.admin.server_admin.conify.config_hard_due_date;" command="cmd_server_admin_config_hard_due_date"/>