</actions>
</permacrud>
</class>
- <class id="aua" controller="open-ils.cstore" oils_obj:fieldmapper="actor::user_address" oils_persist:tablename="actor.usr_address" reporter:label="User Address">
+ <class id="aua" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::user_address" oils_persist:tablename="actor.usr_address" reporter:label="User Address">
<fields oils_persist:primary="id" oils_persist:sequence="actor.usr_address_id_seq">
<field reporter:label="Type" name="address_type" reporter:datatype="text"/>
<field reporter:label="City" name="city" reporter:datatype="text"/>
<link field="usr" reltype="has_a" key="id" map="" class="au"/>
<link field="replaces" reltype="has_a" key="id" map="" class="aua"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="UPDATE_USER"><context link="usr" field="home_ou"/></create>
+ <retrieve permission="VIEW_USER"><context link="usr" field="home_ou"/></retrieve>
+ <update permission="UPDATE_USER"><context link="usr" field="home_ou"/></update>
+ <delete permission="UPDATE_USER"><context link="usr" field="home_ou"/></delete>
+ </actions>
+ </permacrud>
</class>
<class id="aal" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::address_alert" oils_persist:tablename="actor.address_alert" reporter:label="Address Alert">
<fields oils_persist:primary="id" oils_persist:sequence="actor.address_alert_id_seq">
</permacrud>
</class>
- <class id="ssubn" controller="open-ils.cstore" oils_obj:fieldmapper="serial::subscription_note" oils_persist:tablename="serial.subscription_note" reporter:label="Subscription Note">
+ <class id="ssubn" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::subscription_note" oils_persist:tablename="serial.subscription_note" reporter:label="Subscription Note">
<fields oils_persist:primary="id" oils_persist:sequence="serial.subscription_note_id_seq">
<field reporter:label="ID" name="id" reporter:datatype="id"/>
<field reporter:label="Subscription" name="subscription" reporter:datatype="link"/>
<link field="subscription" reltype="has_a" key="id" map="" class="ssub"/>
<link field="creator" reltype="has_a" key="id" map="" class="au"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib">
+ <context link="subscription" field="owning_lib"/>
+ </create>
+ <retrieve />
+ <update permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib">
+ <context link="subscription" field="owning_lib"/>
+ </update>
+ <delete permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib">
+ <context link="subscription" field="owning_lib"/>
+ </delete>
+ </actions>
+ </permacrud>
</class>
<class id="sdist" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::distribution" oils_persist:tablename="serial.distribution" reporter:label="Distribution">
</fields>
<links>
<link field="distribution" reltype="has_a" key="id" map="" class="sdist"/>
- <link field="items" reltype="has_many" key="id" map="" class="sitem"/>
+ <link field="items" reltype="has_many" key="stream" map="" class="sitem"/>
<link field="routing_list_users" reltype="has_many" key="stream" map="" class="srlu"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
</permacrud>
</class>
- <class id="sin" controller="open-ils.cstore" oils_obj:fieldmapper="serial::item_note" oils_persist:tablename="serial.item_note" reporter:label="Item Note">
+ <class id="sin" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::item_note" oils_persist:tablename="serial.item_note" reporter:label="Item Note">
<fields oils_persist:primary="id" oils_persist:sequence="serial.item_note_id_seq">
<field reporter:label="ID" name="id" reporter:datatype="id"/>
<field reporter:label="Item" name="item" reporter:datatype="link"/>
<link field="item" reltype="has_a" key="id" map="" class="sitem"/>
<link field="creator" reltype="has_a" key="id" map="" class="au"/>
</links>
- <!-- Not available via PCRUD at this time -->
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_SERIAL_ITEM">
+ <context link="item" jump="stream.distribution" field="holding_lib" />
+ </create>
+ <retrieve permission="ADMIN_SERIAL_ITEM">
+ <context link="item" jump="stream.distribution" field="holding_lib" />
+ </retrieve>
+ <update permission="ADMIN_SERIAL_ITEM">
+ <context link="item" jump="stream.distribution" field="holding_lib" />
+ </update>
+ <delete permission="ADMIN_SERIAL_ITEM">
+ <context link="item" jump="stream.distribution" field="holding_lib" />
+ </delete>
+ </actions>
+ </permacrud>
</class>
<class id="sasum" controller="open-ils.cstore" oils_obj:fieldmapper="serial::any_summary" oils_persist:tablename="serial.any_summary" reporter:label="All Issues' Summaries" oils_persist:readonly="true">
<fields>
</permacrud>
</class>
+ <class id="spt" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::pattern_template" oils_persist:tablename="serial.pattern_template" reporter:label="Prediction Pattern Template">
+ <fields oils_persist:primary="id" oils_persist:sequence="serial.pattern_template_id_seq">
+ <field reporter:label="ID" name="id" reporter:datatype="id" />
+ <field reporter:label="Name" name="name" reporter:datatype="text" oils_obj:required="true"/>
+ <field reporter:label="Pattern Code" name="pattern_code" reporter:datatype="text" oils_obj:required="true"/>
+ <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit" oils_obj:required="true"/>
+ <field reporter:label="Share Depth" name="share_depth" reporter:datatype="int"/>
+ </fields>
+ <links>
+ <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_SERIAL_PATTERN_TEMPLATE" context_field="owning_lib"/>
+ <retrieve/>
+ <update permission="ADMIN_SERIAL_PATTERN_TEMPLATE" context_field="owning_lib"/>
+ <delete permission="ADMIN_SERIAL_PATTERN_TEMPLATE" context_field="owning_lib"/>
+ </actions>
+ </permacrud>
+ </class>
+
<class id="ascecm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::stat_cat_entry_copy_map" oils_persist:tablename="asset.stat_cat_entry_copy_map" reporter:label="Statistical Category Entry Copy Map">
<fields oils_persist:primary="id" oils_persist:sequence="asset.stat_cat_entry_copy_map_id_seq">
<field name="id" reporter:datatype="id" />
<event code='11009' textcode='SERIAL_STREAM_NOT_EMPTY'>
<desc xml:lang="en-US">The stream still has dependent objects</desc>
</event>
+ <event code='11010' textcode='SERIAL_CAPTION_AND_PATTERN_NOT_EMPTY'>
+ <desc xml:lang="en-US">The prediction pattern still has dependent objects</desc>
+ </event>
</ils_events>
my %found_sdist_ids;
my %found_sstr_ids;
+ my %siss_to_potentially_delete;
for my $item (@$items) {
my $sstr_id = ref $item->stream ? $item->stream->id : $item->stream;
if (!exists($found_sstr_ids{$sstr_id})) {
$item->edit_date('now');
if( $item->isdeleted ) {
+ my $siss_id = ref $item->issuance ? $item->issuance->id : $item->issuance;
+ $siss_to_potentially_delete{$siss_id}++;
$evt = _delete_sitem( $editor, $override, $item);
} elsif( $item->isnew ) {
# TODO: reconsider this
$editor->rollback;
return $evt;
}
+ if( %siss_to_potentially_delete ) {
+ foreach my $id (keys %siss_to_potentially_delete) {
+ my $issuance = $editor->retrieve_serial_issuance([
+ $id, {
+ "flesh" => 1, "flesh_fields" => {
+ "siss" => ["items"],
+ }
+ }
+ ]);
+ unless ($issuance) {
+ $logger->warn("fleshed item-alter failed to retrieve issuance $id to potenitally delete");
+ $editor->rollback;
+ return $editor->die_event;
+ }
+ unless (@{ $issuance->items }) {
+ $logger->info("fleshed item-alter deleting issuance $id as it has no items left");
+ $evt = _delete_siss( $editor, $override, $issuance);
+ if( $evt ) {
+ $logger->info("fleshed item-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
+ $editor->rollback;
+ return $evt;
+ }
+ }
+ }
+ }
$logger->debug("item-alter: done updating item batch");
$editor->commit;
$logger->info("fleshed item-alter successfully updated ".scalar(@$items)." items");
sub make_predictions {
my ($self, $conn, $authtoken, $args) = @_;
- my $editor = OpenILS::Utils::CStoreEditor->new();
my $ssub_id = $args->{ssub_id};
- my $mfhd = MFHD->new(MARC::Record->new());
+ my $editor = OpenILS::Utils::CStoreEditor->new();
my $ssub = $editor->retrieve_serial_subscription([$ssub_id]);
- my $scaps = $editor->search_serial_caption_and_pattern({ subscription => $ssub_id, active => 't'});
my $sdists = $editor->search_serial_distribution( [{ subscription => $ssub->id }, { flesh => 1, flesh_fields => {sdist => [ qw/ streams / ]} }] ); #TODO: 'deleted' support?
+ return store_predictions(
+ $self, $conn, $authtoken, $args, $ssub, $sdists,
+ make_prediction_values($self, $conn, $authtoken, $args, $ssub, $sdists, $editor)
+ );
+}
+
+__PACKAGE__->register_method(
+ method => 'make_prediction_values',
+ api_name => 'open-ils.serial.make_prediction_values',
+ api_level => 1,
+ argc => 1,
+ signature => {
+ desc => 'Receives an ssub id and returns objects that can be used to populate the issuance and item tables',
+ 'params' => [ {
+ name => 'ssub_id',
+ desc => 'Serial Subscription ID',
+ type => 'int'
+ }
+ ]
+ }
+);
+
+sub make_prediction_values {
+ my ($self, $conn, $authtoken, $args, $ssub, $sdists, $editor) = @_;
+ $logger->debug('make_prediction_values with args: ' . OpenSRF::Utils::JSON->perl2JSON($args));
+
+ my $ssub_id = $args->{ssub_id};
+
+ $editor ||= OpenILS::Utils::CStoreEditor->new();
+ $ssub ||= $editor->retrieve_serial_subscription([$ssub_id]);
+ $sdists ||= $editor->search_serial_distribution( [{ subscription => $ssub->id }, { flesh => 1, flesh_fields => {sdist => [ qw/ streams / ]} }] ); #TODO: 'deleted' support?
+
+ my $scaps = $editor->search_serial_caption_and_pattern({ subscription => $ssub_id, active => 't'});
+ my $mfhd = MFHD->new(MARC::Record->new());
+
my $total_streams = 0;
foreach (@$sdists) {
$total_streams += scalar(@{$_->streams});
my $options = {
'caption' => $caption_field,
'scap_id' => $scap->id,
+ 'include_base_issuance' => $args->{include_base_issuance},
'num_to_predict' => $args->{num_to_predict},
'end_date' => defined $args->{end_date} ?
$_strp_date->parse_datetime($args->{end_date}) : undef
};
my $predict_from_siss;
if ($args->{base_issuance}) { # predict from a given issuance
- $predict_from_siss = $args->{base_issuance}->holding_code;
+ $predict_from_siss = $args->{base_issuance};
} else { # default to predicting from last published
my $last_published = $editor->search_serial_issuance([
{'caption_and_pattern' => $scap->id,
);
}
}
+ $logger->debug('make_prediction_values reviving holdings: ' . OpenSRF::Utils::JSON->perl2JSON($predict_from_siss));
$options->{predict_from} = _revive_holding($predict_from_siss->holding_code, $caption_field, 1); # fresh MFHD Record, so we simply default to 1 for seqno
if ($fake_chron_needed) {
$options->{faked_chron_date} = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($predict_from_siss->date_published));
}
+ $logger->debug('make_prediction_values predicting with options: ' . OpenSRF::Utils::JSON->perl2JSON($options));
push( @predictions, _generate_issuance_values($mfhd, $options) );
$link_id++;
}
+ $logger->debug('make_prediction_values predictions: ' . OpenSRF::Utils::JSON->perl2JSON(\@predictions));
+ return \@predictions;
+}
+
+sub store_predictions {
+ my ($self, $conn, $authtoken, $args, $ssub, $sdists, $predictions) = @_;
+
my @issuances;
- foreach my $prediction (@predictions) {
+ foreach my $prediction (@$predictions) {
my $issuance = new Fieldmapper::serial::issuance;
$issuance->isnew(1);
$issuance->label($prediction->{label});
my @items;
for (my $i = 0; $i < @issuances; $i++) {
- my $date_expected = $predictions[$i]->{date_published}->add(seconds => interval_to_seconds($ssub->expected_date_offset))->strftime('%F');
+ my $date_expected = $$predictions[$i]->{date_published}->add(seconds => interval_to_seconds($ssub->expected_date_offset))->strftime('%F');
my $issuance = $issuances[$i];
#$issuance->label(interval_to_seconds($ssub->expected_date_offset));
foreach my $sdist (@$sdists) {
my ($mfhd, $options) = @_;
my $caption = $options->{caption};
my $scap_id = $options->{scap_id};
+ my $include_base_issuance = $options->{include_base_issuance};
my $num_to_predict = $options->{num_to_predict};
my $end_date = $options->{end_date};
my $predict_from = $options->{predict_from}; # MFHD::Holding to predict from
my $faked_chron_date = $options->{faked_chron_date}; # serial does not have a (complete) chronology caption, so add one (temporarily) based on this date
+ $logger->debug('_generate_issuance_values predict_from: ' . OpenSRF::Utils::JSON->perl2JSON($predict_from));
# Only needed for 'real' MFHD records, not our temp records
# my $link_id = $caption->link_id;
# to recreate rather than try to update
$faked_caption = new MFHD::Caption($faked_caption);
$predict_from = new MFHD::Holding($predict_from->seqno, new MARC::Field($predict_from->tag, $predict_from->indicator(1), $predict_from->indicator(2), $predict_from->subfields_list), $faked_caption);
+ $logger->debug('_generate_issuance_values fake predict_from: ' . OpenSRF::Utils::JSON->perl2JSON($predict_from));
}
- my @predictions = $mfhd->generate_predictions({'base_holding' => $predict_from, 'num_to_predict' => $num_to_predict, 'end_date' => $end_date});
+ my @predictions = $mfhd->generate_predictions({
+ 'include_base_issuance' => $include_base_issuance,
+ 'base_holding' => $predict_from,
+ 'num_to_predict' => $num_to_predict,
+ 'end_date' => $end_date
+ });
+ $logger->debug('_generate_issuance_values predictions: ' . OpenSRF::Utils::JSON->perl2JSON(\@predictions));
my $pub_date;
my @issuance_values;
name => 'donor_unit_ids',
desc => 'hash of unit_ids => 1, keyed with ids of any units giving up items',
type => 'hash'
+ },
+ {
+ name => 'extras',
+ desc => 'hash of hashes, circ_mod code and copy_location id, keyed as above',
+ type => 'hash'
}
],
'return' => {
name => 'donor_unit_ids',
desc => 'hash of unit_ids => 1, keyed with ids of any units giving up items',
type => 'hash'
+ },
+ {
+ name => 'extras',
+ desc => 'hash of hashes, circ_mod code and copy_location id, keyed as above',
+ type => 'hash'
}
],
'return' => {
);
sub unitize_items {
- my ($self, $conn, $auth, $items, $barcodes, $call_numbers, $donor_unit_ids) = @_;
+ my ($self, $conn, $auth, $items, $barcodes, $call_numbers, $donor_unit_ids, $extras) = @_;
my $editor = new_editor("authtoken" => $auth, "xact" => 1);
return $editor->die_event unless $editor->checkauth;
}
my %found_stream_ids;
my %found_types;
+ my $prev_loc_setting_map = {};
my %stream_ids_by_unit_id;
if (!exists($found_types{$stream_id})) {
$found_types{$stream_id} = {};
}
- $found_types{$stream_id}->{$scap->type} = 1;
+ $found_types{$stream_id}->{$scap->type} = 1 if ($scap);
# create unit if needed
if ($unit_id == -1 or (!$new_unit_id and $unit_id == -2)) { # create unit per item
$unit->{"note"} = "Item ID: " . $item->id;
return $unit;
}
+
$unit->barcode($barcodes->{$item->id}) if exists($barcodes->{$item->id});
+ $unit->location($extras->{copy_locations}->{$item->id}) if exists($extras->{copy_locations}->{$item->id});
+ $unit->circ_modifier($extras->{circ_mods}->{$item->id}) if exists($extras->{circ_mods}->{$item->id});
+
my $evt = _create_sunit($editor, $unit);
return $evt if $evt;
if ($unit_id == -2) {
my $evt = _update_sitem($editor, undef, $item);
return $evt if $evt;
+
+ if ($mode eq 'receive') {
+ my $sdists = $editor->search_serial_distribution([
+ {"+sstr" => {"id" => $stream_id}},
+ {
+ "join" => {"sstr" => {}},
+ "flesh" => 1,
+ "flesh_fields" => {"sdist" => ["subscription"]}
+ }]);
+
+ #-------------------------------------------------------------------------
+ # The following is copied from open-ils.serial.receive_items.one_unit_per
+
+ # Fetch a list of issuances with received copies already existing
+ # on this distribution (and with the same holding type on the
+ # issuance). This will be used in up to two places: once when building
+ # a summary, once when changing the copy location of the previous
+ # issuance's copy.
+ my $issuances_received = _issuances_received($editor, $item);
+ if ($U->event_code($issuances_received)) {
+ $editor->rollback;
+ return $issuances_received;
+ }
+
+ # Find out if we need to to deal with previous copy location changing.
+ my $ou = $sdists->[0]->holding_lib;
+ unless (exists $prev_loc_setting_map->{$ou}) {
+ $prev_loc_setting_map->{$ou} = $U->ou_ancestor_setting_value(
+ $ou, "serial.prev_issuance_copy_location", $editor
+ );
+ }
+
+ # If there is a previous copy location setting, we need the previous
+ # issuance, from which we can in turn look up the item attached to the
+ # same stream we're on now.
+ if ($prev_loc_setting_map->{$ou}) {
+ if (my $prev_iss =
+ _previous_issuance($issuances_received, $item->issuance)) {
+
+ # Now we can change the copy location of the previous unit,
+ # if needed.
+ return $editor->event if defined $U->event_code(
+ move_previous_unit(
+ $editor, $prev_iss, $item, $prev_loc_setting_map->{$ou}
+ )
+ );
+ }
+ }
+ #-------------------------------------------------------------------------
+ }
+
}
# cleanup 'dead' units (units which are now emptied of their items)
sub _find_or_create_call_number {
my ($e, $lib, $cn_string, $record) = @_;
- # FIXME: should suffix and prefix come into play here?
- my $existing = $e->search_asset_call_number({
- "owning_lib" => $lib,
- "label" => $cn_string,
- "record" => $record,
- "deleted" => "f"
- }) or return $e->die_event;
+ my ($prefix,$suffix) = ('','');
+ if (ref($cn_string)) {
+ ($prefix,$cn_string,$suffix) = @$cn_string;
+ }
+
+ my $existing = $e->search_asset_call_number([{
+ owning_lib => $lib,
+ label => $cn_string,
+ record => $record,
+ deleted => "f",
+ '+acnp' => { label => $prefix },
+ '+acns' => { label => $suffix },
+
+ },{
+ join => { acnp => {}, acns => {} }
+ }]) or return $e->die_event;
if (@$existing) {
return $existing->[0]->id;
return $e->die_event unless
$e->allowed("CREATE_VOLUME", $lib);
+ $prefix = -1 if (!$prefix);
+ $suffix = -1 if (!$suffix);
+
+ if ($prefix ne '-1') {
+ my $acnp = $e->search_asset_call_number_prefix({
+ owning_lib => $lib,
+ label => $prefix,
+ })->[0];
+
+ if (!$acnp) {
+ $acnp = new Fieldmapper::asset::call_number_prefix;
+ $acnp->label($prefix);
+ $acnp->owning_lib($lib);
+ $e->create_asset_call_number_prefix($acnp) or return $e->die_event;
+ $prefix = $e->data->id;
+ } else {
+ $prefix = $acnp->id;
+ }
+ }
+
+ if ($suffix ne '-1') {
+ my $acns = $e->search_asset_call_number_suffix({
+ owning_lib => $lib,
+ label => $suffix,
+ })->[0];
+
+ if (!$acns) {
+ $acns = new Fieldmapper::asset::call_number_suffix;
+ $acns->label($suffix);
+ $acns->owning_lib($lib);
+ $e->create_asset_call_number_suffix($acns) or return $e->die_event;
+ $suffix = $e->data->id;
+ } else {
+ $suffix = $acns->id;
+ }
+ }
+
my $acn = new Fieldmapper::asset::call_number;
$acn->creator($e->requestor->id);
$acn->record($record);
$acn->label($cn_string);
$acn->owning_lib($lib);
+ $acn->prefix($prefix);
+ $acn->suffix($suffix);
$e->create_asset_call_number($acn) or return $e->die_event;
return $e->data->id;
__PACKAGE__->register_method(
method => 'safe_delete',
+ api_name => 'open-ils.serial.caption_and_pattern.safe_delete',
+ signature => q/
+ Deletes an existing caption and pattern object, but only
+ if there are no attached serial issuances.
+ @param authtoken The login session key
+ @param strid The id of the scap to delete
+ @return 1 on success - Event otherwise.
+ /
+);
+
+__PACKAGE__->register_method(
+ method => 'safe_delete',
api_name => 'open-ils.serial.subscription.safe_delete.dry_run',
);
__PACKAGE__->register_method(
method => 'safe_delete',
api_name => 'open-ils.serial.stream.safe_delete.dry_run',
);
+__PACKAGE__->register_method(
+ method => 'safe_delete',
+ api_name => 'open-ils.serial.caption_and_pattern.safe_delete.dry_run',
+);
sub safe_delete {
my( $self, $conn, $authtoken, $id ) = @_;
foreach my $sitem (@{$sstr->items}) {
if ($sitem->status ne 'Expected') {
- return OpenILS::Event->new('SERIAL_STREAM_NOT_EMPTY', payload=>$id);
+ return $e->die_event(OpenILS::Event->new('SERIAL_STREAM_NOT_EMPTY', payload=>$id));
}
if ($sitem->unit && !$U->is_true($sitem->unit->deleted)) {
- return OpenILS::Event->new('SERIAL_STREAM_NOT_EMPTY', payload=>$id);
+ return $e->die_event(OpenILS::Event->new('SERIAL_STREAM_NOT_EMPTY', payload=>$id));
}
}
foreach my $sstr (@{$sdist->streams}) {
foreach my $sitem (@{$sstr->items}) {
if ($sitem->status ne 'Expected') {
- return OpenILS::Event->new('SERIAL_DISTRIBUTION_NOT_EMPTY', payload=>$id);
+ return $e->die_event(OpenILS::Event->new('SERIAL_DISTRIBUTION_NOT_EMPTY', payload=>$id));
}
if ($sitem->unit && !$U->is_true($sitem->unit->deleted)) {
- return OpenILS::Event->new('SERIAL_DISTRIBUTION_NOT_EMPTY', payload=>$id);
+ return $e->die_event(OpenILS::Event->new('SERIAL_DISTRIBUTION_NOT_EMPTY', payload=>$id));
}
}
}
$obj = $sdist;
+ } elsif ($type eq 'caption_and_pattern') {
+ my $scap = $e->retrieve_serial_caption_and_pattern([
+ $id,
+ { flesh => 1, flesh_fields => { scap => ['subscription'] } }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless
+ $e->allowed("ADMIN_SERIAL_CAPTION_PATTERN", $scap->subscription->owning_lib);
+
+ my $issuances = $e->search_serial_issuance([{
+ caption_and_pattern => $id
+ },{
+ flesh => 2,
+ flesh_fields => {
+ siss => ['items'],
+ sitem => ['unit']
+ }
+ }]);
+
+ foreach my $siss (@$issuances) {
+ foreach my $sitem (@{$siss->items}) {
+ if ($sitem->status ne 'Expected') {
+ return $e->die_event(OpenILS::Event->new('SERIAL_CAPTION_AND_PATTERN_NOT_EMPTY', payload=>$id));
+ }
+ if ($sitem->unit && !$U->is_true($sitem->unit->deleted)) {
+ return $e->die_event(OpenILS::Event->new('SERIAL_CAPTION_AND_PATTERN_NOT_EMPTY', payload=>$id));
+ }
+ }
+ }
+
+ $obj = $scap;
+
} else { # subscription
my $sub = $e->retrieve_serial_subscription([
$id, {
foreach my $sstr (@{$sdist->streams}) {
foreach my $sitem (@{$sstr->items}) {
if ($sitem->status ne 'Expected') {
- return OpenILS::Event->new('SERIAL_SUBSCRIPTION_NOT_EMPTY', payload=>$id);
+ return $e->die_event(OpenILS::Event->new('SERIAL_SUBSCRIPTION_NOT_EMPTY', payload=>$id));
}
if ($sitem->unit && !$U->is_true($sitem->unit->deleted)) {
- return OpenILS::Event->new('SERIAL_SUBSCRIPTION_NOT_EMPTY', payload=>$id);
+ return $e->die_event(OpenILS::Event->new('SERIAL_SUBSCRIPTION_NOT_EMPTY', payload=>$id));
}
}
}
$e->$method($obj) or return $e->die_event;
$e->commit;
}
+
return 1;
}
return;
}
+__PACKAGE__->register_method(
+ "method" => "fetch_pattern_templates",
+ "api_name" => "open-ils.serial.pattern_template.retrieve.at",
+ "stream" => 1,
+ "signature" => {
+ "desc" => q{Return the set of pattern templates that are
+ visible to the specified library.},
+ "params" => [
+ {"desc" => "Authtoken", "type" => "string"},
+ {"desc" => "OU ID", "type" => "number"},
+ ],
+ return => {
+ desc => "stream of pattern templates",
+ type => "object", class => "spt"
+ }
+ }
+);
+
+sub fetch_pattern_templates {
+ my ($self, $client, $auth, $org_unit) = @_;
+
+ my $e = new_editor("authtoken" => $auth);
+ return $e->die_event unless $e->checkauth;
+
+ my $patterns = $e->json_query({
+ from => [ 'serial.pattern_templates_visible_to' => $org_unit ]
+ });
+$logger->info(Dumper($patterns)); use Data::Dumper;
+
+ $client->respond($e->retrieve_serial_pattern_template($_->{id}))
+ foreach (@$patterns);
+
+ $e->disconnect;
+ return undef;
+}
+
1;
# generate_predictions()
# Accepts a hash ref of options initially defined as:
# base_holding : reference to the holding field to predict from
+# include_base_issuance : whether to "predict" the startting holding, so as to generate a label for it
# num_to_predict : the number of issues you wish to predict
# OR
# end_holding : holding field ref, keep predicting until you meet or exceed it
my $end_holding = $options->{end_holding};
my $end_date = $options->{end_date};
my $max_to_predict = $options->{max_to_predict} || 10000; # fail-safe
+ my $include_base_issuance = $options->{include_base_issuance};
if (!defined($base_holding)) {
carp("Base holding not defined in generate_predictions, returning empty set");
my $curr_holding = $base_holding->clone; # prevent side-effects
my @predictions;
-
+ push(@predictions, $curr_holding->clone) if ($include_base_issuance);
+
if ($num_to_predict) {
for (my $i = 0; $i < $num_to_predict; $i++) {
push(@predictions, $curr_holding->increment->clone);
label TEXT,
date_published TIMESTAMP WITH TIME ZONE,
caption_and_pattern INT REFERENCES serial.caption_and_pattern (id)
+ ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED,
holding_code TEXT CONSTRAINT issuance_holding_code_check CHECK (
holding_code IS NULL OR could_be_serial_holding_code(holding_code)
CREATE TRIGGER materialize_holding_code
AFTER INSERT OR UPDATE ON serial.issuance
FOR EACH ROW EXECUTE PROCEDURE serial.materialize_holding_code() ;
+
+CREATE TABLE serial.pattern_template (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL,
+ pattern_code TEXT NOT NULL,
+ owning_lib INTEGER REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED,
+ share_depth INTEGER NOT NULL DEFAULT 0
+);
+CREATE INDEX serial_pattern_template_name_idx ON serial.pattern_template (evergreen.lowercase(name));
+
+CREATE OR REPLACE FUNCTION serial.pattern_templates_visible_to(org_unit INT) RETURNS SETOF serial.pattern_template AS $func$
+BEGIN
+ RETURN QUERY SELECT *
+ FROM serial.pattern_template spt
+ WHERE (
+ SELECT ARRAY_AGG(id)
+ FROM actor.org_unit_descendants(spt.owning_lib, spt.share_depth)
+ ) @@ org_unit::TEXT::QUERY_INT;
+END;
+$func$ LANGUAGE PLPGSQL;
+
COMMIT;
( 591, 'ADMIN_COPY_TAG', oils_i18n_gettext( 591,
'Administer copy tag', 'ppl', 'description' )),
( 592,'CONTAINER_BATCH_UPDATE', oils_i18n_gettext( 592,
- 'Allow batch update via buckets', 'ppl', 'description' ))
+ 'Allow batch update via buckets', 'ppl', 'description' )),
+ ( 593, 'ADMIN_SERIAL_PATTERN_TEMPLATE', oils_i18n_gettext( 593,
+ 'Administer serial prediction pattern templates', 'ppl', 'description' ))
;
SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 1000);
'ADMIN_SERIAL_CAPTION_PATTERN',
'ADMIN_SERIAL_DISTRIBUTION',
'ADMIN_SERIAL_ITEM',
+ 'ADMIN_SERIAL_PATTERN_TEMPLATE',
'ADMIN_SERIAL_STREAM',
'ADMIN_SERIAL_SUBSCRIPTION',
'ISSUANCE_HOLDS',
--- /dev/null
+BEGIN;
+
+SELECT plan(6);
+
+INSERT INTO serial.pattern_template(name, pattern_code, owning_lib, share_depth)
+VALUES ('spt-vis-test', '[]', 4, 0);
+
+SELECT is(
+ (SELECT COUNT(*) FROM serial.pattern_templates_visible_to(4)
+ WHERE name = 'spt-vis-test'),
+ 1::BIGINT,
+ 'BR1 can see its own pattern at consortial sharing depth'
+);
+SELECT is(
+ (SELECT COUNT(*) FROM serial.pattern_templates_visible_to(7)
+ WHERE name = 'spt-vis-test'),
+ 1::BIGINT,
+ 'BR4 can see it as well at consortial sharing depth'
+);
+SELECT is(
+ (SELECT COUNT(*) FROM serial.pattern_templates_visible_to(8)
+ WHERE name = 'spt-vis-test'),
+ 1::BIGINT,
+ 'SL1 can see it as well at consortial sharing depth'
+);
+
+UPDATE serial.pattern_template SET share_depth = 2 WHERE name = 'spt-vis-test';
+
+SELECT is(
+ (SELECT COUNT(*) FROM serial.pattern_templates_visible_to(4)
+ WHERE name = 'spt-vis-test'),
+ 1::BIGINT,
+ 'BR1 can still see own pattern at branch sharing depth'
+);
+SELECT is(
+ (SELECT COUNT(*) FROM serial.pattern_templates_visible_to(7)
+ WHERE name = 'spt-vis-test'),
+ 0::BIGINT,
+ 'BR4 CANNOT see it at branch sharing depth'
+);
+SELECT is(
+ (SELECT COUNT(*) FROM serial.pattern_templates_visible_to(8)
+ WHERE name = 'spt-vis-test'),
+ 1::BIGINT,
+ 'SL1 can still see it at branch sharing depth'
+);
+
+ROLLBACK;
--- /dev/null
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+CREATE TABLE serial.pattern_template (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL,
+ pattern_code TEXT NOT NULL,
+ owning_lib INTEGER REFERENCES actor.org_unit(id) DEFERRABLE INITIALLY DEFERRED,
+ share_depth INTEGER NOT NULL DEFAULT 0
+);
+CREATE INDEX serial_pattern_template_name_idx ON serial.pattern_template (evergreen.lowercase(name));
+
+CREATE OR REPLACE FUNCTION serial.pattern_templates_visible_to(org_unit INT) RETURNS SETOF serial.pattern_template AS $func$
+BEGIN
+ RETURN QUERY SELECT *
+ FROM serial.pattern_template spt
+ WHERE (
+ SELECT ARRAY_AGG(id)
+ FROM actor.org_unit_descendants(spt.owning_lib, spt.share_depth)
+ ) @@ org_unit::TEXT::QUERY_INT;
+END;
+$func$ LANGUAGE PLPGSQL;
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES
+ ( 593, 'ADMIN_SERIAL_PATTERN_TEMPLATE', oils_i18n_gettext( 593,
+ 'Administer serial prediction pattern templates', 'ppl', 'description' ))
+;
+
+INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable)
+ SELECT
+ pgt.id, perm.id, aout.depth, FALSE
+ FROM
+ permission.grp_tree pgt,
+ permission.perm_list perm,
+ actor.org_unit_type aout
+ WHERE
+ pgt.name = 'Serials' AND
+ aout.name = 'System' AND
+ perm.code IN (
+ 'ADMIN_SERIAL_PATTERN_TEMPLATE'
+ );
+
+COMMIT;
--- /dev/null
+BEGIN;
+
+ALTER TABLE serial.issuance DROP CONSTRAINT IF EXISTS issuance_caption_and_pattern_fkey;
+
+-- Using NOT VALID and VALIDATE CONSTRAINT limits the impact to concurrent work.
+-- For details, see: https://www.postgresql.org/docs/current/static/sql-altertable.html
+
+ALTER TABLE serial.issuance ADD CONSTRAINT issuance_caption_and_pattern_fkey
+ FOREIGN KEY (caption_and_pattern)
+ REFERENCES serial.caption_and_pattern (id)
+ ON DELETE CASCADE
+ DEFERRABLE INITIALLY DEFERRED
+ NOT VALID;
+
+ALTER TABLE serial.issuance VALIDATE CONSTRAINT issuance_caption_and_pattern_fkey;
+
+COMMIT;
+
,[ l('Notifications / Action Triggers'), "./admin/local/action_trigger/event_definition" ]
,[ l('Patrons with Negative Balances'), "./admin/local/circ/neg_balance_users" ]
,[ l('Search Filter Groups'), "./admin/local/actor/search_filter_group" ]
- ,[ l('Serial Copy Template Editor'), "./admin/local/asset/copy_template" ]
,[ l('Standing Penalties'), "./admin/local/config/standing_penalty" ]
,[ l('Statistical Categories Editor'), "./admin/local/asset/stat_cat_editor" ]
,[ l('Statistical Popularity Badges'), "./admin/local/rating/badge" ]
--- /dev/null
+[%
+ WRAPPER "staff/base.tt2";
+ ctx.page_title = l("Serials Administration");
+ ctx.page_app = "egSerialsAdmin";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/file.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/serials/app.js"></script>
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+ s.SERIALS_TEMPLATE_SUCCESS_SAVE = "[% l('Saved serial template') %]";
+ s.SERIALS_TEMPLATE_SUCCESS_DELETE = "[% l('Deleted serial template') %]";
+ s.SERIALS_TEMPLATE_FAIL_SAVE = "[% l('Failed to save serial template') %]";
+ s.SERIALS_TEMPLATE_FAIL_DELETE = "[% l('Failed to delete serial template') %]";
+ s.LOAN_DURATION_SHORT = "[% l('Short') %]";
+ s.LOAN_DURATION_NORMAL = "[% l('Normal') %]";
+ s.LOAN_DURATION_EXTENDED = "[% l('Extended') %]";
+ s.FINE_LEVEL_LOW = "[% l('Low') %]";
+ s.FINE_LEVEL_NORMAL = "[% l('Normal') %]";
+ s.FINE_LEVEL_HIGH = "[% l('High') %]";
+ s.CONFIRM_DIRTY_EXIT = "[% l('There are unsaved changes; close anyway?') %]";
+}]);
+</script>
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
+
+
--- /dev/null
+[%
+ WRAPPER "staff/base.tt2";
+ ctx.page_title = l("Prediction Pattern Templates");
+ ctx.page_app = "egAdminConfig";
+ ctx.page_ctrl = 'PatternTemplate';
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/fm_record_editor.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/app.js"></script>
+[% INCLUDE 'staff/serials/share/serials_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/services/core.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/prediction_wizard.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/serials/pattern_template.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+ <div class="alert alert-info alert-less-pad strong-text-2">
+ [% l('Prediction Pattern Templates') %]
+ </div>
+</div>
+
+<eg-grid
+ id-field="id"
+ idl-class="spt"
+ grid-controls="gridControls"
+ persist-key="admin.serials.pattern_template">
+
+ <eg-grid-menu-item handler="new_record" label="[% l('New Record') %]"></eg-grid-menu-item>
+ <eg-grid-action handler="edit_record" label="[% l('Edit Record') %]" disabled="need_one_selected"></eg-grid-action>
+ <eg-grid-action handler="delete_selected" label="[% l('Delete Selected') %]"></eg-grid-action>
+
+ <eg-grid-field label="[% l('Name') %]" path="name"></eg-grid-field>
+ <eg-grid-field label="[% l('Pattern Code') %]" path="pattern_code"></eg-grid-field>
+ <eg-grid-field label="[% l('Owning Library') %]" path="owning_lib.name"></eg-grid-field>
+ <eg-grid-field label="[% l('Sharing Depth') %]" path="share_depth"></eg-grid-field>
+ <eg-grid-field label="[% l('ID') %]" path='id' required hidden></eg-grid-field>
+ <eg-grid-field path='*' hidden></eg-grid-field>
+</eg-grid>
+
+[% END %]
--- /dev/null
+<style>
+ .app-modal-window .modal-dialog {
+ width: 800px;
+ }
+ .vertical-align {
+ display: flex;
+ align-items: center;
+ }
+</style>
+
+<form role="form">
+<div class="container-fluid">
+ <div class="row bg-info vertical-align">
+ <div class="col-md-3">
+ <h4>[% l('Template Name') %]</h4>
+ </div>
+ <div class="col-md-3">
+ <input type="text" class="form-control" ng-model="working.name"></input>
+ </div>
+<!-- FIXME: remove for now; may be nice to have later
+ <div class="col-md-2">
+ <div class="btn-group pull-right">
+ <span class="btn btn-default btn-file">
+ [% l('Import') %]
+ <input type="file" eg-file-reader container="imported_template.data">
+ </span>
+ <label class="btn btn-default"
+ eg-json-exporter container="hashed_template"
+ default-file-name="'[% l('exported_serials_template.json') %]'">
+ [% l('Export') %]
+ </label>
+ </div>
+ </div>
+-->
+ <div class="col-md-4">
+ <div class="btn-group pull-right">
+ <button class="btn btn-default" ng-click="clearWorking()" type="button">[% l('Clear') %]</button>
+ <button class="btn btn-primary" ng-disabled="working.name=='' || working.loan_duration == null || working.fine_level == null" ng-click="saveTemplate()" type="button">[% l('Save') %]</label>
+ <button class="btn btn-warning" ng-click="close_modal()" type="button">[% l('Close') %]</label>
+ </div>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-4">
+ <b>[% l('Circulate?') %]</b>
+ </div>
+ <div class="col-md-4">
+ <b>[% l('Status') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-8">
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.circulate !== undefined}">
+ <div class="row">
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.circulate" ng-model="working.circulate" value="t"/>
+ [% l('Yes') %]
+ </label>
+ </div>
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.circulate" ng-model="working.circulate" value="f"/>
+ [% l('No') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.status !== undefined}">
+ <select class="form-control"
+ ng-disabled="!defaults.attributes.status" ng-model="working.status"
+ ng-options="s.id() as s.name() for s in status_list">
+ </select>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Circulation Library') %]</b>
+ </div>
+ <div class="col-md-6">
+ <b>[% l('Reference?') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.circ_lib !== undefined}">
+ <eg-org-selector
+ alldisabled="{{!defaults.attributes.circ_lib}}"
+ selected="working.circ_lib"
+ noDefault
+ label="[% l('(Unset)') %]"
+ disable-test="cant_have_vols"
+ ></eg-org-selector>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.ref !== undefined}">
+ <div class="row">
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.ref" ng-model="working.ref" value="t"/>
+ [% l('Yes') %]
+ </label>
+ </div>
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.ref" ng-model="working.ref" value="f"/>
+ [% l('No') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Shelving Location') %]</b>
+ </div>
+ <div class="col-md-6">
+ <b>[% l('OPAC Visible?') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.location !== undefined}">
+ <select class="form-control"
+ ng-disabled="!defaults.attributes.location" ng-model="working.location"
+ ng-options="l.id() as i18n.ou_qualified_location_name(l) for l in location_list"
+ ></select>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.opac_visible !== undefined}">
+ <div class="row">
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.opac_visible" ng-model="working.opac_visible" value="t"/>
+ [% l('Yes') %]
+ </label>
+ </div>
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.opac_visible" ng-model="working.opac_visible" value="f"/>
+ [% l('No') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Circulation Modifer') %]</b>
+ </div>
+ <div class="col-md-6">
+ <b>[% l('Price') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="nullable col-md-6" ng-class="{'bg-success': working.circ_modifier !== undefined}">
+ <select class="form-control"
+ ng-disabled="!defaults.attributes.circ_modifier" ng-model="working.circ_modifier"
+ ng-options="m.code() as m.name() for m in circ_modifier_list"
+ >
+ <option value="">[% l('<NONE>') %]</option>
+ </select>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.price !== undefined}">
+ <input class="form-control" ng-disabled="!defaults.attributes.price" ng-model="working.price" type="text"/>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Loan Duration') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.loan_duration !== undefined}">
+ <select class="form-control" ng-disabled="!defaults.attributes.loan_duration" ng-model="working.loan_duration" ng-options="x.v() as x.l() for x in loan_duration_options">
+ </select>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Circulate as Type') %]</b>
+ </div>
+ <div class="col-md-6">
+ <b>[% l('Deposit?') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="nullable col-md-6" ng-class="{'bg-success': working.circ_as_type !== undefined}">
+ <select class="form-control"
+ ng-disabled="!defaults.attributes.circ_as_type" ng-model="working.circ_as_type"
+ ng-options="t.code() as t.value() for t in circ_type_list">
+ <option value="">[% l('<NONE>') %]</option>
+ </select>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.deposit !== undefined}">
+ <div class="row">
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.deposit" ng-model="working.deposit" value="t"/>
+ [% l('Yes') %]
+ </label>
+ </div>
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.deposit" ng-model="working.deposit" value="f"/>
+ [% l('No') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Holdable?') %]</b>
+ </div>
+ <div class="col-md-6">
+ <b>[% l('Deposit Amount') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.holdable !== undefined}">
+ <div class="row">
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.holdable" ng-model="working.holdable" value="t"/>
+ [% l('Yes') %]
+ </label>
+ </div>
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.holdable" ng-model="working.holdable" value="f"/>
+ [% l('No') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.deposit_amount !== undefined}">
+ <input class="form-control" ng-disabled="!defaults.attributes.deposit_amount" ng-model="working.deposit_amount" type="text"/>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Age-based Hold Protection') %]</b>
+ </div>
+ <div class="col-md-6">
+ <b>[% l('Quality') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.age_protect !== undefined}">
+ <select class="form-control"
+ ng-disabled="!defaults.attributes.age_protect" ng-model="working.age_protect"
+ ng-options="a.id() as a.name() for a in age_protect_list"
+ ></select>
+ </div>
+ <div class="col-md-6" ng-class="{'bg-success': working.mint_condition !== undefined}">
+ <div class="row">
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.mint_condition" ng-model="working.mint_condition" value="t"/>
+ [% l('Good') %]
+ </label>
+ </div>
+ <div class="col-xs-3">
+ <label>
+ <input type="radio" ng-disabled="!defaults.attributes.mint_condition" ng-model="working.mint_condition" value="f"/>
+ [% l('Damaged') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Fine Level') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.fine_level !== undefined}">
+ <select class="form-control" ng-disabled="!defaults.attributes.fine_level" ng-model="working.fine_level" ng-options="x.v() as x.l() for x in fine_level_options">
+ </select>
+ </div>
+ </div>
+
+ <div class="row pad-vert"></div>
+
+ <div class="row bg-info">
+ <div class="col-md-6">
+ <b>[% l('Floating') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-6" ng-class="{'bg-success': working.floating !== undefined}">
+ <select class="form-control"
+ ng-disabled="!defaults.attributes.floating" ng-model="working.floating"
+ ng-options="a.id() as a.name() for a in floating_list"
+ ></select>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</div>
+</form>
--- /dev/null
+
+<div class="container-fluid" style="text-align:center">
+ <div class="alert alert-info alert-less-pad strong-text-2">
+ <span>[% l('Serials Administration') %]</span>
+ </div>
+</div>
+
+<div class="container admin-splash-container">
+
+[%
+ interfaces = [
+ [ l('Serial Copy Templates'), "./admin/serials/templates" ]
+ [ l('Prediction Pattern Templates'), "./admin/serials/pattern_template" ]
+ ];
+
+ USE table(interfaces, cols=3);
+%]
+
+<div class="row">
+ [% FOREACH col = table.cols %]
+ <div class="col-md-4">
+ [% FOREACH item = col %][% IF item.1 %]
+ <div class="row new-entry">
+ <div class="col-md-12">
+ <span class="glyphicon glyphicon-pencil"></span>
+ <a target="_self" href="[% item.1 %]">
+ [% item.0 %]
+ </a>
+ </div>
+ </div>
+ [% END %]
+ [% END %]
+ </div>
+ [% END %]
+</div>
+
+</div>
+
--- /dev/null
+<eg-grid
+ id-field="id"
+ idl-class="act"
+ features="-sort,-multisort"
+ grid-controls="grid_controls"
+ persist-key="serials.copy_templates">
+
+ <eg-grid-menu-item handler="grid_actions.create_template"
+ label="[% l('Create Template') %]"></eg-grid-menu-item>
+
+ <eg-grid-action handler="grid_actions.edit_template"
+ label="[% l('Edit Template') %]"
+ disabled="need_one_selected"></eg-grid-action>
+
+ <eg-grid-action handler="grid_actions.delete_template"
+ label="[% l('Delete Template') %]"></eg-grid-action>
+
+ <eg-grid-field label="[% l('Template ID') %]" path='id' required></eg-grid-field>
+
+ <eg-grid-field label="[% l('Template Name') %]" path='name'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Create Date') %]"
+ path='create_date'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Creator') %]"
+ path='creator.usrname'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Edit Date') %]"
+ path='edit_date'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Editor') %]"
+ path='editor.usrname'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Owning Library') %]"
+ path='owning_lib.shortname'></eg-grid-field>
+
+ <eg-grid-field label="[% l('Circulating Library') %]"
+ path='circ_lib.shortname' hidden></eg-grid-field>
+
+ <eg-grid-field label="[% l('Status') %]"
+ path='status.name' hidden></eg-grid-field>
+
+ <eg-grid-field label="[% l('Circ Modifier') %]"
+ path='circ_modifier.code' hidden></eg-grid-field>
+
+ <eg-grid-field label="[% l('Location') %]"
+ path='location.name' hidden></eg-grid-field>
+
+ <eg-grid-field label="[% l('Floating') %]"
+ path='floating.name' hidden></eg-grid-field>
+
+ <eg-grid-field path='*' hidden></eg-grid-field>
+</eg-grid>
+
--- /dev/null
+<div class="container-fluid" style="text-align:center">
+ <div class="alert alert-info alert-less-pad strong-text-2">
+ <span>[% l('Serials Templates') %]</span>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-3">
+ <div class="input-group">
+ <span class="input-group-addon">[% l('Owning Library') %]</span>
+ <eg-org-selector selected="context_ou"></eg-org-selector>
+ </div>
+ </div>
+</div>
+
+<div class="pad-vert"></div>
+
+<div>
+[% INCLUDE 'staff/admin/serials/t_template_list.tt2' %]
+</div>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/patron_search.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/tagtable.js"></script>
+[% INCLUDE 'staff/serials/share/serials_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/app.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/services/core.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/sub_selector.js"></script>
[% INCLUDE 'staff/cat/share/marcedit_strings.tt2' %]
<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/marcedit.js"></script>
<script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
"[% l('Item Transfer Target set') %]";
s.MARK_OVERLAY_TARGET =
"[% l('Record Overlay Target set') %]";
+
+ s.SERIALS_NO_SUBS = "[% l('No subscription selected') %]";
+ s.SERIALS_NO_ITEMS = "[% l('No items expected for the selected subscription') %]";
+
+ s.SERIALS_ISSUANCE_FAIL_SAVE = "[% l('Failed to save issuance') %]";
+ s.SERIALS_ISSUANCE_SUCCESS_SAVE = "[% l('Issuance saved') %]";
+
}])
</script>
[% l('Add Volumes') %]
</button>
<div class="btn-group" uib-dropdown dropdown-append-to-body>
+ <button id="serials-button" type="button" class="btn btn-default" uib-dropdown-toggle>
+ [% l('Serials') %] <span class="caret"></span>
+ </button>
+ <ul uib-dropdown-menu role="menu" aria-labelledby="serials-button">
+ <li role="menuitem">
+ <a ng-click="quickReceive()">[% l('Quick Receive') %]</a>
+ </li>
+ <li role="menuitem">
+ <a target="_self" href="./serials/{{record_id}}">[% l('Manage Subscriptions') %]</a>
+ </li>
+ <li role="menuitem">
+ <a target="_self" href="./serials/{{record_id}}/manage-mfhds">[% l('Manage MFHDs') %]</a>
+ </li>
+ </ul>
+ </div>
+ <div class="btn-group" uib-dropdown dropdown-append-to-body>
<button id="mark-for-button" type="button" class="btn btn-default" uib-dropdown-toggle>
[% l('Mark for:') %] <span class="caret"></span>
</button>
</a>
</li>
<li>
+ <a href="./admin/serials/index" target="_self">
+ <span class="glyphicon glyphicon-paperclip"></span>
+ [% l('Serials Administration') %]
+ </a>
+ </li>
+ <li>
<a href="./admin/booking/index" target="_self">
<span class="glyphicon glyphicon-calendar"></span>
[% l('Booking Administration') %]
--- /dev/null
+[%
+ WRAPPER "staff/base.tt2";
+ ctx.page_title = l("Serials Management");
+ ctx.page_app = "egSerialsApp";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/mfhd.js"></script>
+[% INCLUDE 'staff/serials/share/serials_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/services/core.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/app.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/marcrecord.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/tagtable.js"></script>
+[% INCLUDE 'staff/cat/share/marcedit_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/marcedit.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/subscription_manager.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/sub_selector.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/mfhd_manager.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/prediction_manager.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/prediction_wizard.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/item_manager.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/serials/directives/view-items-grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+ s.SERIALS_SUBSCRIPTION_SUCCESS_CLONE = "[% l('Cloned serial subscription') %]";
+ s.SERIALS_SUBSCRIPTION_FAIL_CLONE = "[% l('Failed to clone serial subscription') %]";
+ s.SERIALS_SUBSCRIPTION_SUCCESS_DELETE = "[% l('Deleted serial subscription') %]";
+ s.SERIALS_SUBSCRIPTION_FAIL_DELETE = "[% l('Failed to delete serial subscription') %]";
+ s.SERIALS_DISTRIBUTION_SUCCESS_DELETE = "[% l('Deleted serial distribution') %]";
+ s.SERIALS_DISTRIBUTION_FAIL_DELETE = "[% l('Failed to delete serial distribution') %]";
+ s.SERIALS_STREAM_SUCCESS_DELETE = "[% l('Deleted serial stream') %]";
+ s.SERIALS_STREAM_FAIL_DELETE = "[% l('Failed to delete serial stream') %]";
+ s.SERIALS_SCAP_SUCCESS_DELETE = "[% l('Deleted serial prediction pattern') %]";
+ s.SERIALS_SCAP_FAIL_DELETE = "[% l('Failed to delete serial prediction pattern') %]";
+ s.SERIALS_ISSUANCE_FAIL_SAVE = "[% l('Failed to save issuance') %]";
+ s.SERIALS_ISSUANCE_SUCCESS_SAVE = "[% l('Issuance saved') %]";
+ s.SERIALS_ITEM_NOTE_FAIL_SAVE = "[% l('Failed to save item notes') %]";
+ s.SERIALS_ITEM_NOTE_SUCCESS_SAVE = "[% l('Item notes saved') %]";
+ s.SERIALS_DISTRIBUTION_SUCCESS_LINK_MFHD = "[% l('Distribution linked to MFHD') %]";
+ s.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD = "[% l('Failed to link distribution to MFHD') %]";
+ s.SERIALS_DISTRIBUTION_SUCCESS_BINDING_TEMPLATE = "[% l('Binding unit template applied to Distribution') %]";
+ s.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE = "[% l('Failed to apply binding unit template to distribution') %]";
+ s.SERIALS_EDIT_SISS_HC = "[% l('Edit issue information') %]";
+ s.SERIALS_ISSUANCE_PREDICT = "[% l('Predict New Issues: Initial Values') %]";
+ s.SERIALS_ISSUANCE_ADD = "[% l('Add following issue') %]";
+ s.SERIALS_SPECIAL_ISSUANCE_ADD = "[% l('Add special issue') %]";
+
+ s.CONFIRM_DELETE_SUBSCRIPTION = "[% l('Delete selected subscription(s)?') %]";
+ s.CONFIRM_DELETE_SUBSCRIPTION_MESSAGE = "[% l('Will delete {{count}} subscription(s)') %]";
+ s.CONFIRM_DELETE_DISTRIBUTION = "[% l('Delete selected distribution(s)?') %]";
+ s.CONFIRM_DELETE_DISTRIBUTION_MESSAGE = "[% l('Will delete {{count}} distribution(s)') %]";
+ s.CONFIRM_DELETE_STREAM = "[% l('Delete selected stream(s)?') %]";
+ s.CONFIRM_DELETE_STREAM_MESSAGE = "[% l('Will delete {{count}} stream(s)') %]";
+ s.CONFIRM_DELETE_SCAP = "[% l('Delete prediction pattern?') %]";
+ s.CONFIRM_DELETE_SCAP_MESSAGE = "[% l('Will delete the prediction pattern if there are no attached issuances.') %]";
+
+ s.CONFIRM_CHANGE_ITEMS = {};
+ s.CONFIRM_CHANGE_ITEMS.delete = "[% l('Delete selected item(s)?') %]";
+ s.CONFIRM_CHANGE_ITEMS.reset = "[% l('Reset selected items?') %]"
+ s.CONFIRM_CHANGE_ITEMS.receive = "[% l('Receive selected items?') %]"
+ s.CONFIRM_CHANGE_ITEMS.status = "[% l('Change status selected items?') %]"
+
+ s.CONFIRM_DELETE_MFHDS = "[% l('Delete selected MFHD(s)?') %]";
+ s.CONFIRM_DELETE_MFHDS_MESSAGE = "[% l('Will delete {{items}} MFHD(s).') %]";
+
+}]);
+</script>
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
+
--- /dev/null
+[%# Shared serial strings %]
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+ s.SERIALS_ITEM_STATUS = {};
+ s.SERIALS_ITEM_STATUS.Expected = "[% l('Expected') %]";
+ s.SERIALS_ITEM_STATUS.Received = "[% l('Received') %]";
+ s.SERIALS_ITEM_STATUS.Claimed = "[% l('Claimed') %]";
+ s.SERIALS_ITEM_STATUS.Bindery = "[% l('Bindery') %]";
+ s.SERIALS_ITEM_STATUS.Bound = "[% l('Bound') %]";
+ s.SERIALS_ITEM_STATUS.Discarded = "[% l('Discarded') %]";
+ s.SERIALS_ITEM_STATUS['Not Held'] = "[% l('Not Held' ) %]";
+ s.SERIALS_ITEM_STATUS['Not Published'] = "[% l('Not Published') %]";
+
+ s.CHRON_LABEL_YEAR = "[% l('Year') %]";
+ s.CHRON_LABEL_SEASON = "[% l('Season') %]";
+ s.CHRON_LABEL_MONTH = "[% l('Month') %]";
+ s.CHRON_LABEL_WEEK = "[% l('Week') %]";
+ s.CHRON_LABEL_DAY = "[% l('Day') %]";
+ s.CHRON_LABEL_HOUR = "[% l('Hour') %]";
+ s.EG_CONFIRM_DELETE_PATTERN_TEMPLATE_TITLE = "[% l('Confirm Prediction Pattern Template Deletion') %]";
+ s.EG_CONFIRM_DELETE_PATTERN_TEMPLATE_BODY = "[% l('Delete {{count}} template(s)?') %]";
+ s.PATTERN_TEMPLATE_SUCCESS_DELETE = "[% l('Deleted prediation pattern template(s)') %]";
+ s.PATTERN_TEMPLATE_FAIL_DELETE = "[% l('Failed to delete prediction template(s)') %]";
+}]);
+</script>
+
--- /dev/null
+<form ng-submit="ok(args)" role="form">
+
+<style>
+/* odd/even row styling */
+.modal-body > div:nth-child(odd) {
+ background-color: rgb(248, 248, 248);
+}
+</style>
+
+<div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 class="modal-title" ng-if="rows.length != 1">
+ [% l('Apply Binding Unit Template to [_1] Selected Distributions','{{rows.length}}') %]
+ </h4>
+ <h4 class="modal-title" ng-if="rows.length == 1">
+ [% l('Apply Binding Unit Template to [_1] Selected Distribution','{{rows.length}}') %]
+ </h4>
+</div>
+
+<div class="modal-body">
+ <div class="row">
+ <div class="col-md-8">
+ <label>
+ [% l('Distribution Library') %]
+ </label>
+ </div>
+ <div class="col-md-4">
+ <label>
+ [% l('Binding Unit Template') %]
+ </label>
+ </div>
+ </div>
+ <div class="row" ng-repeat="lib in libs">
+ <div class="col-md-8">
+ <label for="ou_{{lib.id}}">
+ {{lib.name}}
+ </label>
+ </div>
+ <div class="col-md-4">
+ <select id="ou_{{lib.id}}"
+ ng-model="args.bind_unit_template[lib.id]"
+ ng-options="t.id as t.name for t in templates[lib.id]"
+ class="form-control">
+ <option value=""></option>
+ </select>
+ </div>
+ </div>
+</div>
+
+<div class="modal-footer">
+ <input type="submit" class="btn btn-primary" value="[% l('Update') %]"></input>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+</div>
+</form>
--- /dev/null
+<form name="batch_receive_form" ng-submit="ok(items)" role="form">
+<div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 ng-show="force_bind && items.length > 1" class="modal-title">{{ title || "[% l('Bind items') %]" }}</h4>
+ <h4 ng-show="force_bind && items.length <= 1" class="modal-title">{{ title || "[% l('Barcode item') %]" }}</h4>
+ <h4 ng-show="!force_bind" class="modal-title">{{ title || "[% l('Receive items') %]" }}</h4>
+</div>
+
+<div class="modal-body">
+ <div class="row">
+ <div class="col-md-2">
+ <label class="checkbox-inline">
+ <input type="checkbox" ng-model="barcode_items">[% l('Barcode Items') %]
+ </label>
+ </div>
+ <div class="col-md-2">
+ <label class="checkbox-inline">
+ <input type="checkbox" ng-disabled="!barcode_items" ng-model="auto_barcodes">[% l('Auto-Barcode') %]
+ </label>
+ </div>
+ <div class="col-md-2">
+ <label class="checkbox-inline">
+ <input type="checkbox" ng-disabled="" ng-model="print_routing_lists">[% l('Print routing lists') %]
+ </label>
+ </div>
+ <div class="col-md-2">
+ <label class="checkbox-inline" ng-show="items.length > 1">
+ <input type="checkbox" ng-disabled="force_bind" ng-model="bind">[% l('Bind') %]
+ </label>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-12"><hr/></div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-3">
+ <b>[% l('Library : Distribution/Stream') %]</b>
+ <br/>
+ <dl class="dl-horizontal"><dt>[% l('Notes') %]</dt></dl>
+ </div>
+ <div class="col-md-1">
+ <b>[% l('Issuance') %]</b>
+ </div>
+ <div class="col-md-1">
+ <b>[% l('Copy location') %]</b>
+ </div>
+ <div class="col-md-1">
+ <b>[% l('Call number') %]</b>
+ </div>
+ <div class="col-md-2">
+ <b>[% l('Circulation modifier') %]</b>
+ </div>
+ <div class="col-md-1">
+ <b>[% l('Barcode') %]</b>
+ </div>
+ <div class="col-md-1">
+ <b ng-show="!bind">[% l('Receive') %]</b>
+ <b ng-show="bind">[% l('Include') %]</b>
+ </div>
+ <div class="col-md-1">
+ <b>[% l('Routing List') %]</b>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-4"></div>
+ <div class="col-md-1">
+ <select
+ class="form-control"
+ ng-model="selected_copy_location"
+ ng-options="l.id as l.name for l in acpl_list | orderBy:'name'">
+ <option value="">[% l('Template default') %]</option>
+ </select>
+ </div>
+ <div class="col-md-1">
+ <select
+ class="form-control"
+ ng-model="selected_call_number"
+ ng-options="l as fullCNLabel(l) for l in acn_list | orderBy:'label_sortkey'">
+ <option value="">[% l('Default') %]</option>
+ </select>
+ </div>
+ <div class="col-md-1">
+ <select
+ class="form-control"
+ ng-model="selected_circ_mod"
+ ng-options="l.code as l.name for l in ccm_list | orderBy:'name'">
+ <option value="">[% l('Template default') %]</option>
+ </select>
+ </div>
+ <div class="col-md-4"></div>
+ <div class="col-md-1">
+ <div class="btn btn-primary" ng-click="apply_template_overrides()">[% l('Apply') %]</div>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="col-md-12"><hr/></div>
+ </div>
+
+ <div class="row" ng-repeat="item in items">
+ <div class="col-md-3">
+ {{item.stream().distribution().holding_lib().name()}}: {{item.stream().distribution().label()}}/{{item.stream().routing_label()}}
+ <dl class="dl-horizontal">
+ <div ng-repeat="note in item.stream().distribution().subscription().notes()">
+ <div ng-show="note.alert() == 't'">
+ <dt>{{note.title()}}</dt>
+ <dd>{{note.value()}}</dd>
+ </div>
+ </div>
+ <div ng-repeat="note in item.stream().distribution().notes()">
+ <div ng-show="note.alert() == 't'">
+ <dt>{{note.title()}}</dt>
+ <dd>{{note.value()}}</dd>
+ </div>
+ </div>
+ <div ng-repeat="note in item.notes()">
+ <div ng-show="note.alert() == 't'">
+ <dt>{{note.title()}}</dt>
+ <dd>{{note.value()}}</dd>
+ </div>
+ </div>
+ <dl>
+ </div>
+ <div class="col-md-1">
+ {{item.issuance().label()}}
+ </div>
+ <div class="col-md-1">
+ <select
+ ng-disabled="!item._receive || bind_or_none($index)"
+ class="form-control"
+ ng-model="item._copy_location"
+ ng-options="l.id as l.name for l in acpl_list | orderBy:'name'">
+ <option value="">[% l('Template default') %]</option>
+ </select>
+ </div>
+ <div class="col-md-1">
+ <eg-basic-combo-box eg-disabled="!item._receive || bind_or_none($index)" list="acnp_labels" selected="item._cn_prefix" placeholder="[% l('Prefix') %]"></eg-basic-combo-box>
+ <input ng-disabled="!item._receive || bind_or_none($index)" class="form-control" placeholder="[% l('Label') %]"
+ ng-required="item._receive && !bind_or_none($index)" ng-model="item._call_number" type="text"/>
+ <eg-basic-combo-box eg-disabled="!item._receive || bind_or_none($index)" list="acns_labels" selected="item._cn_suffix" placeholder="[% l('Suffix') %]"></eg-basic-combo-box>
+ <br/>
+ </div>
+ <div class="col-md-1">
+ <select
+ ng-disabled="!item._receive || bind_or_none($index)"
+ class="form-control"
+ ng-model="item._circ_mod"
+ ng-options="l.code as l.name for l in ccm_list | orderBy:'name'">
+ <option value="">[% l('Template default') %]</option>
+ </select>
+ </div>
+ <div class="col-md-2">
+ <input ng-disabled="!item._receive || bind_or_none($index) || (barcode_items && !item.stream().distribution().receive_unit_template())" class="form-control" focus-me="$first"
+ ng-model="item._barcode" type="text" id="item_barcode_{{$index}}"
+ ng-required="item._receive && !bind_or_none($index)" eg-enter="focus_next_barcode($index)"/>
+ <div class="alert alert-warning" ng-show="barcode_items && !item.stream().distribution().receive_unit_template()">
+ [% l('Receiving template not set; needed to barcode while receiving') %]
+ </div>
+ </div>
+ <div class="col-md-1">
+ <input type="checkbox" ng-model="item._receive"/>
+ </div>
+ <div class="col-md-1">
+ <input type="checkbox" ng-disabled="!item._receive || cannot_print($index)" ng-model="item._print_routing_list"/>
+ </div>
+ </div>
+
+</div>
+
+<div class="modal-footer">
+ <div class="row">
+ <div class="col-md-8"></div>
+ <div class="col-md-4">
+ <input type="submit" class="btn btn-primary" ng-disabled="batch_receive_form.$error.required.length" value='{{ save_label || "[% l('Save') %]" }}'></input>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+ </div>
+</div>
+</form>
--- /dev/null
+<select ng-model="ngModel">
+ <option
+ ng-repeat="c in options track by c.value" value="{{c.value}}"
+ ng-disabled="c.disabled">{{c.label}}</option>
+</select>
--- /dev/null
+<form ng-submit="ok(args)" role="form">
+ <div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 ng-show="subs.length==1" class="modal-title">[% l('Clone Subscription') %]</h4>
+ <h4 ng-show="subs.length>1" class="modal-title">[% l('Clone Subscriptions') %]</h4>
+ </div>
+ <div class="modal-body">
+ <p>[% l('This feature will clone the selected subscriptions and all of their subscription notes, distributions, distribution notes, captions and patterns, streams, and routing list users.') %]</p>
+ <p>[% l('Holdings-related objects, like issuances, items, units, and summaries will not be cloned.') %]</p>
+ <p ng-show="subs.length == 1">[% l('To which bibliographic record should the new subscription be attached?') %]</p>
+ <p ng-show="subs.length > 1">[% l('To which bibliographic record should the new subscriptions be attached?') %]</p>
+ <div class="row">
+ <div class="col-md-1">
+ <input type="radio" name="which_radio_button" id="same_bib"
+ ng-model="args.which_radio_button" value="same_bib">
+ </input>
+ </div>
+ <div class="col-md-11">
+ <label ng-if="subs.length==1" for="same_bib">
+ [% l('Same record as the selected subscription') %]
+ </label>
+ <label ng-if="subs.length>1" for="same_bib">
+ [% l('Same record as the selected subscriptions') %]
+ </label>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-1">
+ <input type="radio" name="which_radio_button"
+ ng-model="args.which_radio_button" value="different_bib">
+ </input>
+ </div>
+ <div class="col-md-3">
+ <label for="different_bib">
+ [% l('Record specified by this Bid ID:') %]
+ </label>
+ </div>
+ <div class="col-md-8">
+ <input type="number" class="form-control" min="1"
+ ng-click="args.which_radio_button='different_bib'"
+ ng-model-options="{ debounce: 1000 }"
+ id="different_bib" ng-model="args.bib_id"/>
+ <div ng-show="args.bib_id">{{mvr.title}}</div>
+ <div class="alert alert-warning" ng-show="bibNotFound">
+ [% l('Not Found') %]
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <input
+ ng-disabled="!args.which_radio_button||(args.which_radio_button=='different_bib'&&(!args.bib_id||bibNotFound))"
+ type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</form>
--- /dev/null
+<select ng-model="ngModel">
+ <option value="mo">[% l('Monday') %]</option>
+ <option value="tu">[% l('Tuesday') %]</option>
+ <option value="we">[% l('Wednesday') %]</option>
+ <option value="th">[% l('Thursday') %]</option>
+ <option value="fr">[% l('Friday') %]</option>
+ <option value="sa">[% l('Saturday') %]</option>
+ <option value="su">[% l('Sunday') %]</option>
+</select>
--- /dev/null
+<form ng-submit="ok(args)" role="form">
+<div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 class="modal-title">{{ title || "[% l('Construct new holding code') %]" }}</h4>
+</div>
+
+<div class="modal-body">
+ <div class="row">
+ <div class="col-md-3">
+ <b>[% l('Publication date') %]</b>
+ </div>
+ <div class="col-md-4">
+ <eg-date-input ng-model="pubdate"></eg-date-input>
+ </div>
+ <div class="col-md-2">
+ <b>[% l('Type') %]</b>
+ </div>
+ <div class="col-md-3">
+ <select
+ class="form-control"
+ ng-model="type"
+ ng-init='types=[{n:"basic",l:"[%l('Basic')%]"},{n:"supplement",l:"[%l('Supplement')%]"},{n:"index",l:"[%l('Index')%]"}]'
+ ng-options='t.n as t.l for t in types'>
+ </select>
+ </div>
+ </div>
+ <div class="row" ng-show="can_change_adhoc">
+ <div class="col-md-3">
+ <b>[% l('Ad hoc issue?') %]</b>
+ </div>
+ <div class="col-md-1">
+ <input type="checkbox" ng-model="args.adhoc">
+ </div>
+ </div>
+
+ <div ng-show="args.adhoc">
+ <div class="pad-vert row">
+ <div class="col-md-3">
+ <b>[% l('Issuance Label') %]</b>
+ </div>
+ <div class="col-md-9">
+ <input class="form-control" type="text" ng-model="label"/>
+ </div>
+ </div>
+ </div>
+
+ <div ng-hide="args.adhoc">
+ <div class="row container" ng-if="args.enums.length">
+ <hr/>
+ <h2>[% l('Enumeration labels') %]</h2>
+ </div>
+
+ <div class="row" ng-repeat="e in args.enums">
+ <div class="col-md-4">
+ [% l('Enumeration level [_1]','{{ $index + 1}}') %]
+ </div>
+ <div class="col-md-4">
+ <input class="form-control" ng-model="e.value" type="text"/>
+ </div>
+ <div class="col-md-4">
+ {{ e.pattern }}
+ </div>
+ </div>
+
+ <div class="row container" ng-if="args.chrons.length">
+ <hr/>
+ <h2>[% l('Chronology labels') %]</h2>
+ </div>
+
+ <div class="row" ng-repeat="c in args.chrons">
+ <div class="col-md-4">
+ [% l('Chronology level [_1]','{{ $index + 1}}') %]
+ </div>
+ <div class="col-md-4">
+ <input class="form-control" ng-model="c.value" type="text"/>
+ </div>
+ <div class="col-md-4">
+ {{ c.pattern }}
+ </div>
+ </div>
+ </div>
+
+</div>
+
+<div class="modal-footer">
+ <div class="row">
+ <div class="col-md-4" ng-show="request_count">
+ <h4>[% l('Prediction count') %]</h4>
+ </div>
+ <div class="col-md-3" ng-show="request_count">
+ <input class="form-control" ng-model="count" type="number"/>
+ </div>
+ <div class="col-md-5">
+ <input type="submit" class="btn btn-primary" value='{{ save_label || "[% l('Save') %]" }}'></input>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+ </div>
+</div>
+</form>
--- /dev/null
+<div>
+<eg-sub-selector bib-id="bibId" ssub-id="ssubId"></eg-sub-selector>
+</div>
+
+<div>
+<eg-item-grid bib-id="bibId" ssub-id="ssubId"></eg-item-grid>
+</div>
--- /dev/null
+<form ng-submit="ok(args)" role="form">
+ <div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Link MFHD') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div ng-repeat="legacy in legacies">
+ <div uib-tooltip="[% l('Record ID [_1]', '{{legacy.mvr.doc_id}}') %]" tooltip-placement="left">
+ <a target="_blank" href="/eg/staff/cat/catalog/record/{{legacy.mvr.doc_id}}">{{legacy.mvr.title}}</a>
+ </div>
+ <div>
+ {{legacy.mvr.physical_description}}
+ </div>
+ <div ng-repeat="svr in legacy.svrs" uib-tooltip-template="'/eg/staff/serials/t_mfhd_tooltip'" tooltip-placement="left">
+ <input type="radio" name="which_mfhd" ng-model="args.which_mfhd" ng-value="svr.sre_id" id="{{svr.sre_id}}">
+ <label for="{{svr.sre_id}}">
+ {{svr.location}}
+ </label>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <div class="pull-left">
+ <label>[% l('Summary Display') %]</label>
+ <select ng-model="args.summary_method">
+ <option value="add_to_sre" selected>[% l('Both') %]</option>
+ <option value="merge_with_sre">[% l('Merge') %]</option>
+ <option value="use_sre_only">[% l('MFHD Only') %]</option>
+ <option value="use_sdist_only">[% l('None') %]</option>
+ </select>
+ </div>
+ <input type="submit" class="btn btn-primary" value="[% l('OK') %]" ng-disabled="!args.which_mfhd"/>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</form>
--- /dev/null
+<div ng-show="bib_id" class="row col-md-12">
+ <eg-record-summary record-id="bib_id" no-marc-link="true" record="summary_record"></eg-record-summary>
+</div>
+
+<div class="row col-md-12 pad-vert">
+ <div class="col-md-12">
+ <uib-tabset active="active_tab">
+ <!-- note that non-numeric index values must be enclosed in single-quotes,
+ otherwise selecting the active table won't work cleanly -->
+ <uib-tab index="'manage-subscriptions'" heading="[% l('Manage Subscriptions') %]">
+ <div class="container-fluid">
+ <eg-subscription-manager ng-if="active_tab == 'manage-subscriptions'" bib-id="bib_id"></eg-subscription-manager>
+ </div>
+ </uib-tab>
+ <uib-tab index="'prediction'" heading="[% l('Manage Predictions') %]">
+ <eg-prediction-manager ng-if="active_tab == 'prediction'"
+ bib-id="bib_id" ssub-id="ssub.id">
+ </eg-prediction-manager>
+ </uib-tab>
+ <uib-tab index="'issues'" heading="[% l('Manage Issues') %]">
+ <eg-item-manager ng-if="active_tab == 'issues'"
+ bib-id="bib_id" ssub-id="ssub.id">
+ </eg-item-manager>
+ </uib-tab>
+ <uib-tab index="'manage-mfhds'" heading="[% l('Manage MFHDs') %]">
+ <eg-mfhd-manager ng-if="active_tab == 'manage-mfhds'"
+ bib-id="bib_id">
+ </eg-mfhd-manager>
+ </uib-tab>
+ </uib-tabset>
+ </div>
+</div>
--- /dev/null
+<div>
+ <eg-grid
+ id-field="id"
+ features="-display,-sort,-multisort"
+ items-provider="mfhdGridDataProvider"
+ grid-controls="mfhdGridControls"
+ persist-key="serials.mfhd_grid">
+
+ <eg-grid-menu-item handler="createMfhd"
+ label="[% l('Create MFHD') %]"
+ />
+
+ <eg-grid-action handler="edit_mfhd" disabled="need_one_selected"
+ label="[% l('Edit MFHD') %]"></eg-grid-action>
+ <eg-grid-action handler="delete_mfhds"
+ label="[% l('Delete Selected MFHDs') %]"></eg-grid-action>
+
+ <eg-grid-field label="[% l('ID') %]" path="id" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Owning Library') %]" path="owning_lib.name" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Basic Holdings') %]" path="basic_holdings" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Index Holdings') %]" path="index_holdings" hidden></eg-grid-field>
+ <eg-grid-field label="[% l('Supplement Holdings') %]" path="supplement_holdings" hidden></eg-grid-field>
+
+ </eg-grid>
+
+</div>
--- /dev/null
+<div class="row">
+ <div class="col-md-4">
+ [% l('Record ID') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.sre_id }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ [% l('Basic Holdings') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.basic_holdings | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ </div>
+ <div class="col-md-8">
+ {{ svr.basic_holdings_add | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ [% l('Supplement Holdings') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.supplement_holdings | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ </div>
+ <div class="col-md-8">
+ {{ svr.supplement_holdings_add | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ [% l('Index Holdings') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.index_holdings | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ </div>
+ <div class="col-md-8">
+ {{ svr.index_holdings_add | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ [% l('Online') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.online | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ [% l('Missing') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.missing | join:' ; ' }}
+ </div>
+</div>
+<div class="row">
+ <div class="col-md-4">
+ [% l('Incomplete') %]
+ </div>
+ <div class="col-md-8">
+ {{ svr.incomplete | join:' ; ' }}
+ </div>
+</div>
--- /dev/null
+<div class="input-group">
+ <input type="text"
+ class="form-control"
+ ng-show="!hideDatePicker"
+ uib-datepicker-popup="MMMM d"
+ is-open="datePickerIsOpen"
+ ng-model="dt"
+ datepicker-options="options"
+ show-button-bar="false"
+ />
+ <span class="input-group-btn">
+ <button type="button" class="btn btn-default"
+ ng-click="datePickerIsOpen=!datePickerIsOpen">
+ <i class="glyphicon glyphicon-calendar"></i>
+ </button>
+ </span>
+</div>
--- /dev/null
+<select ng-model="ngModel">
+ <option value="01">[% l('January') %]</option>
+ <option value="02">[% l('February') %]</option>
+ <option value="03">[% l('March') %]</option>
+ <option value="04">[% l('April') %]</option>
+ <option value="05">[% l('May') %]</option>
+ <option value="06">[% l('June') %]</option>
+ <option value="07">[% l('July') %]</option>
+ <option value="08">[% l('August') %]</option>
+ <option value="09">[% l('September') %]</option>
+ <option value="10">[% l('October') %]</option>
+ <option value="11">[% l('November') %]</option>
+ <option value="12">[% l('December') %]</option>
+</select>
--- /dev/null
+<form ng-submit="ok(note)" role="form">
+ <div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 ng-if="note_type == 'subscription'" class="modal-title">[% l('New Subscription Note') %]</h4>
+ <h4 ng-if="note_type == 'distribution'" class="modal-title">[% l('New Distribution Note') %]</h4>
+ <h4 ng-if="note_type == 'item'" class="modal-title">[% l('New Item Note') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-md-6">
+ <input class="form-control" type="text"
+ ng-model="note.title" placeholder="[% l('Title...') %]"/>
+ </div>
+ <div class="col-md-3">
+ <label>
+ <input type="checkbox" ng-model="note.pub"/>
+ [% l('Public Note') %]
+ </label>
+ <label>
+ <input type="checkbox" ng-model="note.alert"/>
+ [% l('Alert Note') %]
+ </label>
+ </div>
+ </div>
+ <div class="row pad-vert">
+ <div class="col-md-12">
+ <textarea class="form-control"
+ ng-model="note.value" placeholder="[% l('Note...') %]">
+ </textarea>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <div class="row">
+ <div class="col-md-2">
+ <input type="text" class="form-control" ng-hide="!require_initials"
+ ng-model="initials" placeholder="[% l('Initials') %]" ng-required="require_initials"/>
+ </div>
+ <div class="col-md-10 pull-right">
+ <input type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
+ <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
+ </div>
+ </div>
+
+ <div class="row pad-vert" ng-if="note_list.length > 0">
+ <div class="col-md-12">
+ <div class="row">
+ <div class="col-md-12">
+ <hr/>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <h4 ng-if="note_type == 'subscription'" class="pull-left">[% l('Existing Subscription Notes') %]</h4>
+ <h4 ng-if="note_type == 'distribution'" class="pull-left">[% l('Existing Distribution Notes') %]</h4>
+ <h4 ng-if="note_type == 'item'" class="pull-left">[% l('Existing Item Notes') %]</h4>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="row" ng-repeat="n in note_list" ng-init="pub = n.pub() == 't'; alert = n.alert() == 't'; title = n.title(); value = n.value(); deleted = n.isdeleted()">
+ <div class="col-md-12">
+ <div class="row">
+ <div class="col-md-6">
+ <input class="form-control" type="text" ng-change="n.title(title) && n.ischanged(1)"
+ ng-model="title" placeholder="[% l('Title...') %]" ng-disabled="deleted"/>
+ </div>
+ <div class="col-md-3">
+ <label>
+ <input type="checkbox" ng-model="pub" ng-change="n.pub(pub) && n.ischanged(1)" ng-disabled="deleted"/>
+ [% l('Public Note') %]
+ </label>
+ <label>
+ <input type="checkbox" ng-model="alert" ng-change="n.alert(alert) && n.ischanged(1)" ng-disabled="deleted"/>
+ [% l('Alert Note') %]
+ </label>
+ </div>
+ <div class="col-md-3">
+ <label>
+ <input type="checkbox" ng-model="deleted" ng-change="n.isdeleted(deleted)"/>
+ [% l('Deleted?') %]
+ </label>
+ </div>
+ </div>
+ <div class="row pad-vert">
+ <div class="col-md-12">
+ <textarea class="form-control" ng-change="n.value(value) && n.ischanged(1)"
+ ng-model="value" placeholder="[% l('Note...') %]" ng-disabled="deleted">
+ </textarea>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-12">
+ <hr/>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</form>
--- /dev/null
+<!-- use <form> so we get submit-on-enter for free -->
+<div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Edit Prediction Pattern') %]</h4>
+ </div>
+ <div class="modal-body">
+ <div class="container-fluid">
+ <eg-prediction-wizard pattern-code="patternCode" on-save="ok"
+ show-share="showShare" view-only="viewOnly"
+ ></eg-prediction-wizard pattern-code>
+ </div>
+ </div>
+</div> <!-- modal-content -->
--- /dev/null
+<div class="container prediction_pattern_summary">
+ <div class="row" ng-if="pattern.use_enum">
+ [% l('Enumeration captions:') %]
+ {{pattern.display_enum_captions()}}
+ </div>
+ <div class="row" ng-if="pattern.use_alt_enum">
+ [% l('Alternative enumeration captions:') %]
+ {{pattern.display_alt_enum_captions()}}
+ </div>
+ <div class="row" ng-if="pattern.use_chron">
+ [% l('Chronology captions:') %]
+ {{pattern.display_chron_captions()}}
+ </div>
+ <div class="row" ng-if="pattern.use_alt_chron">
+ [% l('Alternative chronology captions:') %]
+ {{pattern.display_alt_chron_captions()}}
+ </div>
+ <div class="row">
+ [% l('Frequency:') %]
+ <span ng-if="pattern.frequency_type == 'preset'">
+ <span ng-switch="pattern.frequency_preset">
+ <span ng-switch-when="d">[% l('Daily') %]</span>
+ <span ng-switch-when="w">[% l('Weekly (Weekly)') %]</span>
+ <span ng-switch-when="c">[% l('2 x per week (Semiweekly)') %]</span>
+ <span ng-switch-when="i">[% l('3 x per week (Three times a week)') %]</span>
+ <span ng-switch-when="e">[% l('Every two weeks (Biweekly)') %]</span>
+ <span ng-switch-when="m">[% l('Monthly') %]</span>
+ <span ng-switch-when="s">[% l('2 x per month (Semimonthly)') %]</span>
+ <span ng-switch-when="j">[% l('3 x per month (Three times a month)') %]</span>
+ <span ng-switch-when="b">[% l('Every other month (Bimonthly)') %]</span>
+ <span ng-switch-when="q">[% l('Quarterly') %]</span>
+ <span ng-switch-when="f">[% l('2 x per year (Semiannual)') %]</span>
+ <span ng-switch-when="t">[% l('3 x per year (Three times a year)') %]</span>
+ <span ng-switch-when="a">[% l('Yearly (Annual)') %]</span>
+ <span ng-switch-when="g">[% l('Every other year (Biennial)') %]</span>
+ <span ng-switch-when="h">[% l('Every three years (Triennial)') %]</span>
+ <span ng-switch-when="x">[% l('Completely irregular') %]</span>
+ <span ng-switch-when="k">[% l('Continuously updated') %]</span>
+ </span>
+ </span>
+ <span ng-if="pattern.frequency_type == 'numeric'">
+ [% l('[_1] issues per year', '{{pattern.frequency_numeric}}') %]
+ </span>
+ </div>
+ <div class="row" ng-if="pattern.use_regularity">
+ [% l('Specifies regularity adjustments') %]
+ </div>
+</div>
--- /dev/null
+<div>
+<eg-sub-selector bib-id="bibId" ssub-id="ssubId"></eg-sub-selector>
+</div>
+
+<div>
+ <div class="form-inline pad-vert">
+ <button class="btn btn-warning" ng-click="startNewScap()">[% l('Add New') %]</button>
+ <button class="btn btn-warning" ng-click="importScapFromBibRecord()" ng-disabled="!has_pattern_to_import">[% l('Import from Bibliographic and/or MFHD Records') %]</button>
+ <button class="btn btn-warning" ng-click="importScapFromSpt()">[% l('Create from Template') %]</button>
+ <select class="form-control" ng-model="active_pattern_template.id" ng-options="spt.id as spt.name for spt in pattern_templates | orderBy:'name'">
+ </select>
+ </div>
+ <div class="row" ng-if="new_prediction">
+ <ng-form name="forms.newpredform" class="form-inline">
+ <div class="col-md-1"></div>
+ <div class="col-md-1">
+ <label class="checkbox-inline">
+ <input type="checkbox" ng-model="new_prediction.active">[% l('Active') %]
+ </label>
+ </div>
+ <div class="col-md-2">
+ <label>[% l('Start Date') %]</label>
+ {{new_prediction.create_date | date:"shortDate"}}
+ </div>
+ <div class="col-md-3">
+ <label>[% l('Type') %]</label>
+ <select class="form-control" ng-model="new_prediction.type">
+ <option value="basic">[% l('Basic') %]</option>
+ <option value="supplement">[% l('Supplement') %]</option>
+ <option value="index">[% l('Index') %]</option>
+ </select>
+ <button class="btn btn-default" ng-if="new_prediction.pattern_code === null"
+ ng-click="openPatternEditorDialog(new_prediction, forms.newpredform)">[% l('Create Pattern') %]</button>
+ <button class="btn btn-default" ng-if="new_prediction.pattern_code !== null"
+ ng-click="openPatternEditorDialog(new_prediction, forms.newpredform)">[% l('Edit Pattern') %]</button>
+ </div>
+ <div>
+ <button type="submit" class="btn btn-default" ng-click="cancelNewScap()">[% l('Cancel') %]</button>
+ <button type="submit" class="btn btn-primary" ng-disabled="(new_prediction.pattern_code === null) || !forms.newpredform.$dirty" ng-click="createScap(new_prediction)">[% l('Create') %]</button>
+ </div>
+ </form>
+ </div>
+ <h3>[% l('Existing Prediction Patterns') %]</h3>
+ <div class="row" ng-repeat="pred in predictions | orderBy: 'id' as filtered track by pred.id">
+ <ng-form name="forms['predform' + pred.id]" class="form-inline">
+ <div class="col-md-1"><label>[% l('ID') %] {{pred.id}}</label></div>
+ <div class="col-md-1">
+ <label class="checkbox-inline">
+ <input type="checkbox" ng-model="pred.active">[% l('Active') %]
+ </label>
+ </div>
+ <div class="col-md-2">
+ <label>[% l('Start Date') %]</label>
+ {{pred.create_date | date:"shortDate"}}
+ </div>
+ <div class="col-md-3">
+ <label>[% l('Type') %]</label>
+ <select class="form-control" ng-model="pred.type">
+ <option value="basic">[% l('Basic') %]</option>
+ <option value="supplement">[% l('Supplement') %]</option>
+ <option value="index">[% l('Index') %]</option>
+ </select>
+ <button class="btn btn-default" ng-click="openPatternEditorDialog(pred, forms['predform' + pred.id], false)" ng-if=" pred._can_edit_or_delete">[% l('Edit Pattern') %]</button>
+ <button class="btn btn-default" ng-click="openPatternEditorDialog(pred, forms['predform' + pred.id], true)" ng-if="!pred._can_edit_or_delete">[% l('View Pattern') %]</button>
+ </div>
+ <div>
+ <button class="btn btn-default" ng-disabled="forms['predform' + pred.id].$dirty" ng-click="add_issuances()">[% l('Predict New Issues') %]</button>
+ <button type="submit" class="btn btn-default" ng-disabled="!pred._can_edit_or_delete" ng-click="deleteScap(pred)">[% l('Delete') %]</button>
+ <button type="submit" class="btn btn-primary" ng-disabled="!forms['predform' + pred.id].$dirty" ng-click="updateScap(pred)">[% l('Save') %]</button>
+ </div>
+ </form>
+ </div>
+</div>
--- /dev/null
+<div>
+ <div class="pull-right">
+ <div>
+ <button class="btn btn-warning" ng-click="tab.active = tab.active - 1"
+ ng-disabled="tab.active <= 0">
+ [% l('Back') %]
+ </button>
+ <button class="btn btn-success" ng-click="tab.active = tab.active + 1"
+ ng-disabled="!viewOnly && ((tab.active == 0 && tab.enum_form.$invalid) || (tab.active == 1 && tab.chron_form.$invalid) || (tab.active == 3 && tab.freq_form.$invalid))"
+ ng-if="tab.active != 4">
+ [% l('Next') %]
+ </button>
+ <button class="btn btn-primary" ng-click="handle_save()"
+ ng-if="!viewOnly && tab.active == 4">
+ [% l('Save') %]
+ </button>
+ </div>
+ </div>
+ <uib-tabset active="tab.active">
+ <uib-tab index="0" disable="tab.active != 0" heading="[% l('Enumeration Labels') %]">
+ <form name="tab.enum_form">
+ <fieldset ng-disabled="viewOnly">
+ <div class="row">
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="pattern.use_enum" ng-value="True">
+ [% l('Use Enumeration (e.g., v.1, no. 1)') %]
+ </label>
+ <eg-help-popover help-text="[% l('Use this if the serial includes volume or some other form of numbering.') %]">
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="pattern.use_enum" ng-value="False">
+ [% l('Use Calendar Dates Only (e.g., April 10)') %]
+ </label>
+ <eg-help-popover help-text="[% l('Use this if serial issues are referred to only by publication dates (or months or seasons).') %]">
+ </div>
+ <div class="row" ng-if="pattern.use_enum">
+ <div class="row" ng-repeat="enum_level in pattern.enum_levels">
+ <div class="col-md-1"></div>
+ <div class="col-md-1">[% l('Level [_1]', '{{$index + 1}}') %]</div>
+ <div class="col-md-2"><input type="text" ng-model="enum_level.caption" required></div>
+ <div ng-if="$index > 0">
+ <div class="col-md-3">
+ <select ng-model="enum_level.units_per_next_higher.type">
+ <option value="number">[% l('Number') %]</option>
+ <option value="var">[% l('Varies') %]</option>
+ <option value="und">[% l('Undetermined') %]</option>
+ </select>
+ <input type="number" step="1"
+ ng-model="enum_level.units_per_next_higher.value"
+ ng-hide="enum_level.units_per_next_higher.type != 'number'"
+ >
+ </div>
+ <div class="col-md-2">
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="enum_level.restart" ng-value="True">
+ [% l('Restarts at unit completion') %]
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="enum_level.restart" ng-value="False">
+ [% l('Increments continuously') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3" ng-if="$last">
+ <button class="btn btn-warning btn-sm"
+ ng-if="pattern.enum_levels.length > 1"
+ ng-click="pattern.drop_enum_level()">
+ [% ('Remove Level') %]
+ </button>
+ <button class="btn btn-warning btn-sm"
+ ng-disabled="pattern.enum_levels.length >= 6"
+ ng-click="pattern.add_enum_level()">
+ [% ('Add Level') %]
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div ng-if="pattern.use_enum" class="row">
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="pattern.use_alt_enum">
+ [% l('Add alternative enumeration') %]
+ </label>
+ <eg-help-popover help-text="[% l('If a serials is labeled in two different ways, use this to specify the second set of enumeration labels') %]">
+ </div>
+ <div class="row" ng-if="pattern.use_alt_enum">
+ <div class="row" ng-repeat="alt_enum_level in pattern.alt_enum_levels">
+ <div class="col-md-1"></div>
+ <div class="col-md-1">[% l('Level [_1]', '{{$index + 1}}') %]</div>
+ <div class="col-md-2"><input type="text" required ng-model="alt_enum_level.caption"></div>
+ <div ng-if="$index > 0">
+ <div class="col-md-3">
+ <select ng-model="alt_enum_level.units_per_next_higher.type">
+ <option value="number">[% l('Number') %]</option>
+ <option value="var">[% l('Varies') %]</option>
+ <option value="und">[% l('Undetermined') %]</option>
+ </select>
+ <input type="number" step="1"
+ ng-model="alt_enum_level.units_per_next_higher.value"
+ ng-hide="alt_enum_level.units_per_next_higher.type != 'number'"
+ >
+ </div>
+ <div class="col-md-2">
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="alt_enum_level.restart" ng-value="True">
+ [% l('Restarts at unit completion') %]
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="alt_enum_level.restart" ng-value="False">
+ [% l('Increments continuously') %]
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3" ng-if="$last">
+ <button class="btn btn-warning btn-sm"
+ ng-if="pattern.alt_enum_levels.length > 1"
+ ng-click="pattern.drop_alt_enum_level()">
+ [% ('Remove Level') %]
+ </button>
+ <button class="btn btn-warning btn-sm"
+ ng-disabled="pattern.alt_enum_levels.length >= 2"
+ ng-click="pattern.add_alt_enum_level()">
+ [% ('Add Level') %]
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div ng-if="pattern.use_enum" class="row">
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="pattern.use_calendar_change">
+ [% l('First level enumeration changes during subscription year') %]
+ </label>
+ <eg-help-popover help-text="[% l('For example, if the title has two volumes a year, use this to specify the month that the next volume starts.') %]">
+ </div>
+ <div ng-if="pattern.use_calendar_change">
+ <div class="row" ng-repeat="chg in pattern.calendar_change">
+ <div class="col-md-1"></div>
+ <div class="col-md-2">
+ <label>[% l('Change occurs') %]
+ <select ng-model="chg.type">
+ <option value="date">[% l('Specific date') %]</option>
+ <option value="month">[% l('Start of month') %]</option>
+ <option value="season">[% l('Start of season') %]</option>
+ </select>
+ </label>
+ </div>
+ <div class="col-md-3">
+ <eg-month-selector ng-model="chg.month" ng-if="chg.type == 'month'" ></eg-month-selector>
+ <eg-season-selector ng-model="chg.season" ng-if="chg.type == 'season'"></eg-season-selector>
+ <eg-month-day-selector day="chg.day" month="chg.month" ng-if="chg.type == 'date'" ></eg-month-day-selector>
+ </div>
+ <div class="col-md-2">
+ <button ng-click="pattern.remove_calendar_change($index)" class="btn btn-sm btn-warning">[% l('Delete') %]</button>
+ <button ng-click="pattern.add_calendar_change()" ng-hide="!$last" class="btn btn-sm btn-warning">[% l('Add more') %]</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </form>
+ </uib-tab>
+ <uib-tab index="1" disable="tab.active != 1" heading="[% l('Chronology Display') %]">
+ <form name="tab.chron_form">
+ <fieldset ng-disabled="viewOnly">
+ <div>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="pattern.use_chron">
+ [% l('Use Chronology Captions?') %]
+ </label>
+ </div>
+ <div ng-if="pattern.use_chron">
+ <div class="row">
+ <div class="col-md-4"></div>
+ <div class="col-md-4">[% l('Display level descriptor? E.g., "Year: 2017, Month: Feb" (not recommended)') %]</div>
+ </div>
+ <div class="row" ng-repeat="chron in pattern.chron_levels">
+ <div class="col-md-1"></div>
+ <div class="col-md-1">[% l('Level [_1]', '{{$index + 1}}') %]</div>
+ <div class="col-md-2">
+ <eg-chron-selector ng-model="chron.caption" required chron-level="$index" linked-selector="chron_captions">
+ </div>
+ <div class="col-md-2">
+ <input type="checkbox" ng-model="chron.display_caption"></input>
+ </div>
+ <div class="col-md-4">
+ <button ng-if="$index > 0 && $last" ng-click="pattern.drop_chron_level()" class="btn btn-sm btn-warning">
+ [% l('Remove Level') %]
+ </button>
+ <button ng-if="$last && pattern.chron_levels.length < 4" ng-click="pattern.add_chron_level()" class="btn btn-sm btn-warning">
+ [% l('Add Level') %]
+ </button>
+ </div>
+ </div>
+ <div>
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="pattern.use_alt_chron">
+ [% l('Use Alternative Chronology Captions?') %]
+ </label>
+ </div>
+ <div ng-if="pattern.use_alt_chron">
+ <div class="row" ng-repeat="chron in pattern.alt_chron_levels">
+ <div class="col-md-1"></div>
+ <div class="col-md-1">[% l('Level [_1]', '{{$index + 1}}') %]</div>
+ <div class="col-md-2">
+ <eg-chron-selector ng-model="chron.caption" required chron-level="$index" linked-selector="alt_chron_captions">
+ </div>
+ <div class="col-md-2">
+ <input type="checkbox" ng-model="chron.display_caption"></input>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </form>
+ </uib-tab>
+ <uib-tab index="2" disable="tab.active != 2" heading="[% l('MFHD Indicators') %]">
+ <form name="tab.ind_form">
+ <fieldset ng-disabled="viewOnly">
+ <div class="row">
+ <div class="col-md-6">
+ <label for="selectCompressExpand">[% l('Compression Display Options') %]
+ <eg-help-popover help-link="https://www.loc.gov/marc/holdings/hd853855.html"
+ help-text="[% l('Whether the pattern can be used to compress and expand detailed holdings statements.') %]">
+ </label>
+ <select ng-model="pattern.compress_expand">
+ <option value="0">[% l('Cannot compress or expand') %]</option>
+ <option value="1">[% l('Can compress but not expand') %]</option>
+ <option value="2">[% l('Can compress or expand') %]</option>
+ <option value="3">[% l('Unknown') %]</option>
+ </select>
+ </div>
+ <div class="col-md-6">
+ <label for="selectCompressExpand">[% l('Caption Evaluation') %]
+ <eg-help-popover help-link="https://www.loc.gov/marc/holdings/hd853855.html"
+ help-text="[% l('Completeness of the caption levels and whether the captions used actually appear on the bibliographic item.') %]">
+ </label>
+ <select ng-model="pattern.caption_evaluation">
+ <option value="0">[% l('Captions verified; all levels present') %]</option>
+ <option value="1">[% l('Captions verified; all levels may not be present') %]</option>
+ <option value="2">[% l('Captions unverified; all levels present') %]</option>
+ <option value="3">[% l('Captions unverified; all levels may not be present') %]</option>
+ </select>
+ </div>
+ </div>
+ </fieldset>
+ </form>
+ </uib-tab>
+ <uib-tab index="3" disable="tab.active != 3" heading="[% l('Frequency and Regularity') %]">
+ <form name="tab.freq_form">
+ <fieldset ng-disabled="viewOnly">
+ <div class="row">
+ <div class="col-md-2">
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="pattern.frequency_type" value="preset">
+ [% l('Pre-selected') %]
+ </label>
+ </div>
+ <div class="radio">
+ <label>
+ <input type="radio" ng-model="pattern.frequency_type" value="numeric">
+ [% l('Use number of issues per year') %]
+ </label>
+ </div>
+ </div>
+ <div class="col-md-2">
+ <div ng-if="pattern.frequency_type == 'preset'">
+ <select ng-model="pattern.frequency_preset" required>
+ <option value="d">[% l('Daily') %]</option>
+ <option value="w">[% l('Weekly (Weekly)') %]</option>
+ <option value="c">[% l('2 x per week (Semiweekly)') %]</option>
+ <option value="i">[% l('3 x per week (Three times a week)') %]</option>
+ <option value="e">[% l('Every two weeks (Biweekly)') %]</option>
+ <option value="m">[% l('Monthly') %]</option>
+ <option value="s">[% l('2 x per month (Semimonthly)') %]</option>
+ <option value="j">[% l('3 x per month (Three times a month)') %]</option>
+ <option value="b">[% l('Every other month (Bimonthly)') %]</option>
+ <option value="q">[% l('Quarterly') %]</option>
+ <option value="f">[% l('2 x per year (Semiannual)') %]</option>
+ <option value="t">[% l('3 x per year (Three times a year)') %]</option>
+ <option value="a">[% l('Yearly (Annual)') %]</option>
+ <option value="g">[% l('Every other year (Biennial)') %]</option>
+ <option value="h">[% l('Every three years (Triennial)') %]</option>
+ <option value="x">[% l('Completely irregular') %]</option>
+ <option value="k">[% l('Continuously updated') %]</option>
+ </select>
+ </div>
+ <div ng-if="pattern.frequency_type == 'numeric'">
+ <input ng-model="pattern.frequency_numeric" type="number" step="1" required>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="pattern.use_regularity">
+ [% l('Use specific regularity information?') %]
+ </label>
+ <em>[% l('(combined issues, skipped issues, etc.)') %]</em>
+ </div>
+ <div class="row" ng-if="pattern.use_regularity">
+ <div class="row pad-vert" ng-repeat="reg in pattern.regularity">
+ <div class="col-md-2">
+ <button ng-click="pattern.remove_regularity($index)"
+ class="btn btn-sm btn-warning">
+ [% l('Remove Regularity') %]
+ </button>
+ <button ng-if="$last" ng-click="pattern.add_regularity()"
+ class="btn btn-sm btn-warning">
+ [% l('Add Regularity') %]
+ </button>
+ </div>
+ <div class="col-md-1">
+ <select ng-model="reg.regularity_type">
+ <option value="p">[% l('Published') %]</option>
+ <option value="o">[% l('Omitted') %]</option>
+ <option value="c">[% l('Combined') %]</option>
+ </select>
+ </div>
+ <div class="col-md-1">
+ <select ng-model="reg.chron_type">
+ <option value="d">[% l('Day') %]</option>
+ <option value="w">[% l('Week') %]</option>
+ <option value="m">[% l('Month') %]</option>
+ <option value="s">[% l('Season') %]</option>
+ <option value="y">[% l('Year') %]</option>
+ </select>
+ </div>
+ <div class="col-md-6">
+ <div class="row" ng-repeat="part in reg.parts">
+ <div class="col-md-8" ng-if="reg.regularity_type == 'c'">
+ <label>[% l('Combined issue code') %] <input type="text" ng-model="part.combined_code"></label>
+ </div>
+ <div class="col-md-8" ng-if="reg.regularity_type != 'c'">
+ <div ng-if="reg.chron_type == 's'">
+ <label>[% l('Every') %] <eg-season-selector ng-model="part.season"></eg-season-selector></label>
+ </div>
+ <div ng-if="reg.chron_type == 'm'">
+ <label>[% l('Every') %] <eg-month-selector ng-model="part.month"></eg-month-selector></label>
+ </div>
+ <div ng-if="reg.chron_type == 'd'">
+ <select ng-model="part.sub_type">
+ <option value="day_of_month">[% l('On day of month') %]</option>
+ <option value="specific_date">[% l('On specific date') %]</option>
+ <option value="day_of_week">[% l('On day of week') %]</option>
+ </select>
+ <div ng-if="part.sub_type == 'day_of_month'">
+ <input type="number" step="1" min="1" max="31" ng-model="part.day_of_month">
+ </div>
+ <div ng-if="part.sub_type == 'specific_date'">
+ <eg-month-day-selector day="part.day" month="part.month"></eg-month-day-selector>
+ </div>
+ <div ng-if="part.sub_type == 'day_of_week'">
+ <eg-day-of-week-selector ng-model="part.day_of_week"></eg-day-of-week-selector>
+ </div>
+ </div>
+ <div ng-if="reg.chron_type == 'w'">
+ <select ng-model="part.sub_type">
+ <option value="week_in_month">[% l('Week and month') %]</option>
+ <option value="week_day">[% l('Week and day') %]</option>
+ <option value="week_day_in_month">[% l('Week, month, and day') %]</option>
+ </select>
+ <div ng-if="part.sub_type == 'week_in_month'">
+ <eg-week-in-month-selector ng-model="part.week"></eg-week-in-month-selector>
+ [% l('week in') %]
+ <eg-month-selector ng-model="part.month"></eg-month-selector>
+ </div>
+ <div ng-if="part.sub_type == 'week_day'">
+ <eg-week-in-month-selector ng-model="part.week"></eg-week-in-month-selector>
+ [% l('week on') %]
+ <eg-day-of-week-selector ng-model="part.day"></eg-day-of-week-selector>
+ </div>
+ <div ng-if="part.sub_type == 'week_day_in_month'">
+ <eg-week-in-month-selector ng-model="part.week"></eg-week-in-month-selector>
+ [% l('week on') %]
+ <eg-day-of-week-selector ng-model="part.day"></eg-day-of-week-selector>
+ [% l('in') %]
+ <eg-month-selector ng-model="part.month"></eg-month-selector>
+ </div>
+ </div>
+ <div ng-if="reg.chron_type == 'y'">
+ <input type="number" min="1" max="9999" ng-model="part.year">
+ </div>
+ </div>
+ <div class="col-md-4">
+ <button ng-click="pattern.remove_regularity_part(reg, $index)"
+ class="btn btn-xs btn-warning">
+ [% l('Remove Part') %]
+ </button>
+ <button ng-if="$last" ng-click="pattern.add_regularity_part(reg)"
+ class="btn btn-xs btn-warning">
+ [% l('Add Part') %]
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </form>
+ </uib-tab>
+ <uib-tab index="4" disable="tab.active != 4" heading="[% l('Review') %]">
+ <div class="row">
+ <div class="col-md-2">
+ <span class="strong-text-2">[% l('Raw Pattern Code') %]</span>
+ <a class="pull-right" href ng-click="show_pattern_code = false"
+ title="[% l('Hide Raw Pattern Code') %]"
+ ng-show="show_pattern_code">
+ <span class="glyphicon glyphicon-resize-small"></span>
+ </a>
+ <a class="pull-right" href ng-click="show_pattern_code = true"
+ title="[% l('Show Raw Pattern Code') %]"
+ ng-hide="show_pattern_code">
+ <span class="glyphicon glyphicon-resize-full"></span>
+ </a>
+ </div>
+ <div class="col-md-6" ng-show="show_pattern_code">
+ <pre>{{pattern.compile_stringify()}}</pre>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-2">
+ <span class="strong-text-2">[% l('Pattern Summary') %]</span>
+ </div>
+ <div class="col-md-6">
+ <eg-prediction-pattern-summary pattern="pattern"></eg-prediction-pattern-summary>
+ </div>
+ </div>
+ <hr/>
+ <div class="row" ng-if="showShare && !viewOnly">
+ <div class="col-md-6">
+ <label for="pattern_name">[% l('Share this pattern using name') %]</label>
+ <input id="pattern_name" type="text" ng-model="share.pattern_name">
+ </div>
+ <div class="col-md-6">
+ <label for="share_depth">[% l('Share with') %]</label>
+ <eg-share-depth-selector id="share_depth" ng-model="share.depth"></eg-share-depth-selector>
+ </div>
+ </div>
+ <hr/>
+ </uib-tab>
+ </uib-tabset>
+</div>
--- /dev/null
+<form ng-submit="ok()" role="form">
+<div class="modal-body">
+ <eg-embed-frame handlers="xulg" url="url" afterload="page_init"/>
+</div>
+
+<div class="modal-footer">
+ <div class="row">
+ <div class="col-md-10"></div>
+ <div class="col-md-2">
+ <input type="submit" ng-show="last" class="btn btn-primary" value='[% l('Done') %]'></input>
+ <input type="submit" ng-show="!last" class="btn btn-primary" value='[% l('Next') %]'></input>
+ </div>
+ </div>
+</div>
+</form>
--- /dev/null
+<form ng-submit="ok(list)" role="form">
+<div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 class="modal-title">{{ title }}</h4>
+</div>
+
+<div class="modal-body">
+ <div class="row">
+ <div class="col-md-12">
+ <span ng-show="{{mode == 'delete'}}">[% l('Will delete {{items}} item(s).') %]</span>
+ <span ng-show="{{mode == 'reset'}}">[% l('Will reset {{items}} item(s) to Expected and remove unit(s).') %]</span>
+ <span ng-show="{{mode == 'receive'}}">[% l('Will receive {{items}} item(s) without barcoding.') %]</span>
+ <span ng-show="{{mode == 'status'}}">[% l('Will change status of {{items}} item(s).') %]</span>
+ </div>
+ </div>
+
+ <div ng-show="{{ssub_alerts.length > 0}}">
+ <div class="pad-vert row">
+ <div class="col-md-12">
+ <b>[% l('Subscription alerts') %]</b>
+ </div>
+ </div>
+ <div class="row" ng-repeat="note in ssub_alerts">
+ <div class="col-md-12">
+ <dl class="dl-horizontal">
+ <dt>{{note.title()}}</dt>
+ <dd>{{note.value()}}</dd>
+ <dl>
+ </div>
+ </div>
+ </div>
+
+ <div ng-show="{{sdist_alerts.length > 0}}">
+ <div class="pad-vert row">
+ <div class="col-md-12">
+ <b>[% l('Item alerts') %]</b>
+ </div>
+ </div>
+ <div class="row" ng-repeat="note in sdist_alerts">
+ <div class="col-md-12">
+ <dl class="dl-horizontal">
+ <dt>{{note.title()}}</dt>
+ <dd>{{note.value()}}</dd>
+ <dl>
+ </div>
+ </div>
+ </div>
+
+ <div ng-show="{{sitem_alerts.length > 0}}">
+ <div class="pad-vert row">
+ <div class="col-md-12">
+ <b>[% l('Item alerts') %]</b>
+ </div>
+ </div>
+ <div class="row" ng-repeat="note in sitem_alerts">
+ <div class="col-md-12">
+ <dl class="dl-horizontal">
+ <dt>{{note.title()}}</dt>
+ <dd>{{note.value()}}</dd>
+ <dl>
+ </div>
+ </div>
+ </div>
+
+</div>
+
+<div class="modal-footer">
+ <div class="row">
+ <div class="col-md-12">
+ <input type="submit" class="btn btn-primary" value='[% l('OK/Continue') %]'></input>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+ </div>
+</div>
+</form>
--- /dev/null
+<form ng-submit="ok(args)" role="form">
+<div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 class="modal-title">
+ [% l('Manage Routing List for [_1]','{{stream_label}}') %]
+ </h4>
+</div>
+<style>
+/* odd/even row styling */
+.modal-header > div:nth-child(odd) {
+ background-color: rgb(248, 248, 248);
+}
+.strike {
+ text-decoration: line-through;
+}
+</style>
+<div class="modal-header">
+ <div ng-repeat="route in routes">
+ <div class="row">
+ <div class="col-md-2">
+ <span>
+ [% l('[_1].','{{route.pos + 1}}') %]
+ </span>
+ </div>
+ <div class="col-md-8">
+ <span ng-show="route.reader" ng-class="route.delete_me ? 'strike' : ''">
+ {{route.reader.family_name}}, {{route.reader.first_given_name}}
+ ({{route.reader.home_ou.shortname}})
+ </span>
+ <span ng-show="route.department" ng-class="route.delete_me ? 'strike' : ''">
+ {{route.department}}
+ </span>
+ </div>
+ <div class="col-md-2">
+ <span>
+ <a href ng-click="move_route_up(route)">↑</a>
+ <a href ng-click="move_route_down(route)">↓</a>
+ <a href ng-click="toggle_delete(route)">×</a>
+ </span>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-2">
+ </div>
+ <div class="col-md-8" ng-class="route.delete_me ? 'strike' : ''">
+ {{route.note}}
+ </div>
+ <div class="col-md-2">
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="modal-body">
+ <div class="row">
+ <div class="col-md-1">
+ <input type="radio" name="which_radio_button"
+ ng-model="args.which_radio_button" value="reader">
+ </input>
+ </div>
+ <div class="col-md-3">
+ <label for="reader">
+ [% l('Reader (barcode):') %]
+ </label>
+ </div>
+ <div class="col-md-8">
+ <input type="text" ng-model="args.reader" id="reader" class="form-control"
+ ng-click="args.which_radio_button='reader'" focus-me="readerInFocus"
+ ng-model-options="{ debounce: 1000 }">
+ </input>
+ <span ng-show="args.reader && !readerNotFound">{{reader_obj.family_name}}, {{reader_obj.first_given_name}}</span>
+ <span class="alert alert-warning" ng-show="readerNotFound">
+ [% l('Not Found') %]
+ </span>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-1">
+ <input type="radio" name="which_radio_button"
+ ng-model="args.which_radio_button" value="department">
+ </input>
+ </div>
+ <div class="col-md-3">
+ <label for="department">
+ [% l('Department:') %]
+ </label>
+ </div>
+ <div class="col-md-8">
+ <input type="text" ng-model="args.department" id="department" class="form-control"
+ ng-click="args.which_radio_button='department'">
+ </input>
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-md-1">
+ </div>
+ <div class="col-md-3">
+ <label for="note">[% l('Note:') %]</label>
+ </div>
+ <div class="col-md-8">
+ <input ng-model="args.note" type="text" id="note" class="form-control"></input>
+ </div>
+ </div>
+</div>
+
+<div class="modal-footer">
+ <button type="button" class="btn btn-primary pull-left"
+ ng-click="add_route()"
+ ng-disabled="(args[args.which_radio_button] == '')||(args.which_radio_button=='reader'&&readerNotFound)"
+ >
+ [% l('Add Route') %]
+ </button>
+ <input type="submit" class="btn btn-primary" ng-disabled="!model_has_changed"
+ value="[% l('Update') %]"></input>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+</div>
+</form>
--- /dev/null
+<select ng-model="ngModel">
+ <option value="21">[% l('Spring') %]</option>
+ <option value="22">[% l('Summer') %]</option>
+ <option value="23">[% l('Autumn') %]</option>
+ <option value="24">[% l('Winter') %]</option>
+</select>
--- /dev/null
+<form ng-submit="ok()" role="form">
+<div class="modal-header">
+ <button type="button" class="close" ng-click="cancel()"
+ aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Select Patterns to Import') %]</h4>
+</div>
+
+<div class="modal-body">
+ <div ng-repeat="pot in potentials" class="row">
+ <div>
+ <div class="col-md-1">
+ <input type="checkbox" ng-model="pot.selected">
+ </div>
+ <div class="col-md-11">
+ <span ng-if="pot._classname == 'bre'">[% l('Bibliographic record [_1]', '{{pot.id}}') %]</span>
+ <span ng-if="pot._classname == 'sre'">[% l('MFHD record [_1]', '{{pot.id}}') %]</span>
+ </div>
+ </div>
+ <div>
+ <div class="col-md-1"></div>
+ <div class="col-md-11">
+ <pre>{{pot.desc}}</pre>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="modal-footer">
+ <input type="submit" class="btn btn-primary" value="[% l('Import') %]"></input>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+</div>
+</form>
--- /dev/null
+<div class="form-inline">
+<label for="choose-subscription-ou-filter">[% l('At') %]</label>
+<eg-org-selector selected="owning_ou" onchange="owning_ou_changed"
+ sticky-setting="serials.sub_selector.owning_ou_selector"
+>
+</eg-org-selector>
+<label for="choose-subscription">[% l('select subscription to work on') %]</label>
+<select class="form-control" id="choose-subscription" ng-model="ssubId">
+ <option ng-repeat="ssub in subscriptions | orderBy: 'id' as filtered track by ssub.id"
+ value="{{ssub.id}}">
+ [% l('Subscription [_1] at [_2] ([_3] - [_4])',
+ '{{ssub.id}}', '{{ssub.owning_lib.shortname()}}',
+ '{{ssub.start_date | date:"shortDate"}}',
+ '{{ssub.end_date | date:"shortDate"}}') %]
+ </option>
+</select>
+</div>
--- /dev/null
+<div>
+ <label>[% l('Subscriptions owned by or below') %]</label>
+ <eg-org-selector selected="owning_ou" onchange="owning_ou_changed"
+ sticky-setting="serials.ssub_owning_lib_filter">
+ </eg-org-selector>
+ <span class="alert alert-warning" ng-show="subscriptions.length == 0">
+ [% l('No subscriptions are owned by this library') %]
+ </span>
+</div>
+<form name="ssubform" class="pad-vert">
+ <div class="form-inline" ng-repeat="ssub in subscriptions">
+ <div class="row form-inline">
+ <div class="form-group col-sm-2">
+ [% l('#[_1]', '{{ssub.id}}') %]
+ <label>[% l('Owned By') %]</label>
+ <eg-org-selector selected="ssub.owning_lib"></eg-org-selector>
+ </div>
+ <div class="form-group col-sm-3">
+ <div class="row">
+ <div class="form-group col-lg-6">
+ <label class="pull-right">[% l('Start Date') %]</label>
+ </div>
+ <div class="form-group col-lg-6">
+ <div class="pull-left"><eg-date-input ng-model="ssub.start_date" focus-me="ssub._focus_me"></eg-date-input></div>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-sm-3">
+ <div class="row">
+ <div class="form-group col-lg-6">
+ <label class="pull-right">[% l('End Date') %]</label>
+ </div>
+ <div class="form-group col-lg-6">
+ <div class="pull-left"><eg-date-input ng-model="ssub.end_date"></eg-date-input></div>
+ </div>
+ </div>
+ </div>
+ <div class="form-group col-sm-3">
+ <label>[% l('Expected Offset') %]
+ <eg-help-popover help-text="[% l('The difference between the nominal publishing date of an issue and the date that you expect to receive your copy.') %]">
+ </label>
+ <input class="form-control" type="text" ng-model="ssub.expected_date_offset"></input>
+ </div>
+ <div class="form-group col-sm-1">
+ <button class="btn btn-sm btn-warning" ng-click="add_distribution(ssub, true)">[% l('Add distribution') %]</button>
+ </div>
+ </div>
+ <div class="row form-inline pad-vert" ng-repeat="sdist in ssub.distributions">
+ <div class="row">
+ <div class="col-sm-1">
+ <button class="btn btn-xs btn-danger" ng-if="sdist._isnew && ssub.distributions.length > 1"
+ ng-click="remove_pending_distribution(ssub, sdist)"
+ >[% l('Remove') %]</button>
+ </div>
+ <div class="col-sm-2">
+ <label>[% l('Distributed At') %]</label>
+ <eg-org-selector selected="sdist.holding_lib"></eg-org-selector>
+ </div>
+ <div class="col-sm-3">
+ <label>[% l('Label') %]</label>
+ <input class="form-control" type="text" required ng-model="sdist.label" focus-me="sdist._focus_me"></input>
+ </div>
+ <div class="col-sm-2">
+ <label>[% l('OPAC Display') %]
+ <eg-help-popover help-text="[% l('Whether the public catalog display of issues should be grouped by chronology (e.g., years) or enumeration (e.g., volume and number).') %]">
+ </label>
+ <select class="form-control" required ng-model="sdist.display_grouping">
+ <option value="chron">[% l('Chronological') %]</option>
+ <option value="enum" >[% l('Enumeration') %]</option>
+ </select>
+ </div>
+ <div class="col-sm-3">
+ <label>[% l('Receiving Template') %]</label>
+ <select class="form-control" ng-model="sdist.receive_unit_template"
+ ng-options="t.id as t.name for t in receiving_templates[sdist.holding_lib.id()]">
+ <option value=""></option>
+ </select>
+ </div>
+ <div class="col-sm-1" style="padding-left:0"><!-- Yes, it's terrible. But, nested grid alignment... -->
+ <button class="btn btn-sm btn-info" ng-click="add_stream(sdist, true)">[% l('Add copy stream') %]</button>
+ </div>
+ </div>
+ <div class="row form-inline pad-vert">
+ <div class="row form-inline" ng-repeat="sstr in sdist.streams">
+ <div class="col-sm-1"></div>
+ <div class="col-sm-1">
+ <button class="btn btn-xs btn-danger" ng-if="sstr._isnew && sdist.streams.length > 1"
+ ng-click="remove_pending_stream(sdist, sstr)"
+ >[% l('Remove') %]</button>
+ </div>
+ <div class="col-sm-8">
+ <label>[% l('Send to') %]</label>
+ <eg-basic-combo-box list="localStreamNames" on-select="dirtyForm" selected="sstr.routing_label" focus-me="sstr._focus_me"></eg-basic-combo-box>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row form-inline pad-vert"></div>
+ </div>
+ <div class="row form-inline">
+ <button class="btn btn-warning pull-left" ng-click="add_subscription()">[% l('New Subscription') %]</button>
+ <div class="btn-group pull-right">
+ <button class="btn btn-default" ng-disabled="!ssubform.$dirty" ng-click="abort_changes(ssubform)">[% l('Cancel') %]</button>
+ <button class="btn btn-primary" ng-disabled="!ssubform.$dirty" ng-click="save_subscriptions(ssubform)">[% l('Save') %]</button>
+ </div>
+ </div>
+ <div class="row pad-vert"></div>
+</form>
+<div>
+ <eg-grid
+ id-field="index"
+ features="-display,-sort,-multisort"
+ items-provider="distStreamGridDataProvider"
+ grid-controls="distStreamGridControls"
+ persist-key="serials.dist_stream_grid">
+
+ <eg-grid-action handler="apply_binding_template"
+ label="[% l('Apply Binding Template') %]"></eg-grid-action>
+ <eg-grid-action handler="additional_routing" disabled="need_one_selected"
+ label="[% l('Additional Routing') %]"></eg-grid-action>
+ <eg-grid-action handler="subscription_notes" disabled="need_one_selected"
+ label="[% l('Subscription Notes') %]"></eg-grid-action>
+ <eg-grid-action handler="distribution_notes" disabled="need_one_selected"
+ label="[% l('Distribution Notes') %]"></eg-grid-action>
+ <eg-grid-action handler="link_mfhd" disabled="need_one_selected"
+ label="[% l('Link MFHD') %]"></eg-grid-action>
+ <eg-grid-action handler="delete_subscription"
+ label="[% l('Delete Subscription') %]"></eg-grid-action>
+ <eg-grid-action handler="delete_distribution"
+ label="[% l('Delete Distribution') %]"></eg-grid-action>
+ <eg-grid-action handler="delete_stream"
+ label="[% l('Delete Stream') %]"></eg-grid-action>
+ <eg-grid-action handler="clone_subscription"
+ label="[% l('Clone Subscription') %]"></eg-grid-action>
+
+ <eg-grid-field label="[% l('Owning Library') %]" path="owning_lib.name" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Distribution Library') %]" path="sdist.holding_lib.name" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Distribution Label') %]" path="sdist.label" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Copy Stream') %]" path="sstr.id" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Offset') %]" path="expected_date_offset" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Start Date') %]" path="start_date" datatype="timestamp" visible></eg-grid-field>
+ <eg-grid-field label="[% l('End Date') %]" path="end_date" datatype="timestamp" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Route To') %]" path="sstr.routing_label" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Additional Routing') %]" path="sstr.additional_routing" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Receiving Template') %]" path="sdist.receive_unit_template.name"></eg-grid-field>
+ <eg-grid-field label="[% l('MFHD ID') %]" path="sdist.record_entry" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Summary Display') %]" path="sdist.summary_method" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Receiving Call Number') %]" path="sdist.receive_call_number.label"></eg-grid-field>
+ <eg-grid-field label="[% l('Binding Call Number') %]" path="sdist.bind_call_number.label"></eg-grid-field>
+ <eg-grid-field label="[% l('Binding Template') %]" path="sdist.bind_unit_template.name"></eg-grid-field>
+ <eg-grid-field label="[% l('Unit Label Prefix') %]" path="sdist.unit_label_prefix"></eg-grid-field>
+ <eg-grid-field label="[% l('Unit Label Suffix') %]" path="sdist.unit_label_suffix"></eg-grid-field>
+ <eg-grid-field label="[% l('Display Grouping') %]" path="sdist.display_grouping"></eg-grid-field>
+ <eg-grid-field label="[% l('Subscription ID') %]" path="id"></eg-grid-field>
+ <eg-grid-field label="[% l('Distribution ID') %]" path="sdist.id"></eg-grid-field>
+ </eg-grid>
+</div>
--- /dev/null
+<div>
+ <eg-grid
+ id-field="id"
+ features="-display,-sort,-multisort"
+ items-provider="itemGridProvider"
+ grid-controls="itemGridControls"
+ menu-label="[% l('Filter items... ') %]"
+ persist-key="serials.view_item_grid">
+
+ <eg-grid-menu-item handler="filter_items_all"
+ label="[% l('All') %]"></eg-grid-menu-item>
+
+ <eg-grid-menu-item handler="filter_items_have"
+ label="[% l('Held') %]"></eg-grid-menu-item>
+
+ <eg-grid-menu-item handler="filter_items_dont_have"
+ label="[% l('Not Held') %]"></eg-grid-menu-item>
+
+ <eg-grid-menu-item divider="true"></eg-grid-menu-item>
+
+ <eg-grid-menu-item ng-repeat="status in svc.item_status_i18n"
+ label="[% l('Status:') %] {{status.label}}" handler-data="status"
+ handler="filter_items_by_status"></eg-grid-menu-item>
+
+
+ <eg-grid-menu-item handler="receive_next" standalone="true"
+ label="[% l('Receive Next') %]"></eg-grid-menu-item>
+
+ <eg-grid-menu-item handler="add_issuances" standalone="true"
+ label="[% l('Predict New Issues') %]"></eg-grid-menu-item>
+
+ <eg-grid-menu-item handler="add_special_issuance" standalone="true"
+ label="[% l('Add Special Issue') %]"></eg-grid-menu-item>
+
+ <eg-grid-menu-item handler="checkbox_handler"
+ label="[% l('Barcode on receive') %]"
+ checkbox="receive_and_barcode"
+ checked="receive_and_barcode"/>
+
+ <eg-grid-menu-item handler="checkbox_handler"
+ label="[% l('Print routing lists') %]"
+ checkbox="do_print_routing_lists"
+ checked="do_print_routing_lists"/>
+
+
+<!-- Hiding this for now ... seems unnecessary?
+ <eg-grid-menu-item handler="checkbox_handler"
+ label="[% l('Bind on receive') %]"
+ checkbox="receive_and_bind"
+ checked="receive_and_bind"/>
+-->
+
+
+ <eg-grid-action handler="menu_print_routing_lists"
+ label="[% l('Print routing lists') %]"></eg-grid-action>
+
+ <eg-grid-action handler="receive_selected"
+ disabled="need_expected"
+ label="[% l('Receive selected') %]"></eg-grid-action>
+
+ <eg-grid-action handler="bind_selected"
+ disabled="need_one_selected"
+ label="[% l('Barcode selected') %]"></eg-grid-action>
+
+ <eg-grid-action handler="bind_selected"
+ disabled="need_many_selected"
+ label="[% l('Bind selected') %]"></eg-grid-action>
+
+ <eg-grid-action handler="following_issuance"
+ disabled="need_one_selected"
+ label="[% l('Add following issue') %]"></eg-grid-action>
+
+ <eg-grid-action handler="edit_issuance_holding_code"
+ label="[% l('Edit issue holding codes') %]"></eg-grid-action>
+
+ <eg-grid-action handler="set_selected_as_claimed"
+ label="[% l('Mark as claimed') %]"></eg-grid-action>
+ <eg-grid-action handler="set_selected_as_discarded"
+ label="[% l('Mark as discarded') %]"></eg-grid-action>
+ <eg-grid-action handler="set_selected_as_not_published"
+ label="[% l('Mark as not published') %]"></eg-grid-action>
+ <eg-grid-action handler="set_selected_as_not_held"
+ label="[% l('Mark as not held') %]"></eg-grid-action>
+
+ <eg-grid-action handler="item_notes"
+ label="[% l('Item Notes') %]"></eg-grid-action>
+
+ <eg-grid-action handler="reset_selected"
+ label="[% l('Reset items') %]"></eg-grid-action>
+
+ <eg-grid-action handler="delete_items"
+ label="[% l('Delete items') %]"></eg-grid-action>
+
+ <eg-grid-field label="[% l('Distribution Library') %]" path="stream.distribution.holding_lib.name" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Issuance') %]" path="issuance.label" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Barcode') %]" path="unit.barcode" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Publication Date') %]" path="issuance.date_published" visible>{{item.issuance.date_published|date:'shortDate'}}</eg-grid-field>
+ <eg-grid-field label="[% l('Status') %]" path="status" sortable visible></eg-grid-field>
+ <eg-grid-field label="[% l('Date Expected') %]" path="date_expected" sortable visible>{{item.date_expected|date:'shortDate'}}</eg-grid-field>
+ <eg-grid-field label="[% l('Date Received') %]" path="date_received" sortable visible>{{item.date_received|date:'shortDate'}}</eg-grid-field>
+ <eg-grid-field label="[% l('Holding Type') %]" path="issuance.holding_type" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Route To') %]" path="stream.routing_label"></eg-grid-field>
+ <eg-grid-field label="[% l('Receiving Template') %]" path="stream.distribution.receive_unit_template.name" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Summary Display') %]" path="stream.distribution.summary_method" visible></eg-grid-field>
+ <eg-grid-field label="[% l('Receiving Call Number') %]" path="stream.distribution.receive_call_number.label"></eg-grid-field>
+ <eg-grid-field label="[% l('Binding Call Number') %]" path="stream.distribution.bind_call_number.label"></eg-grid-field>
+ <eg-grid-field label="[% l('Binding Template') %]" path="stream.distribution.bind_unit_template.name"></eg-grid-field>
+ <eg-grid-field label="[% l('Unit Label Prefix') %]" path="stream.distribution.unit_label_prefix"></eg-grid-field>
+ <eg-grid-field label="[% l('Unit Label Suffix') %]" path="stream.distribution.unit_label_suffix"></eg-grid-field>
+ <eg-grid-field label="[% l('Display Grouping') %]" path="stream.distribution.display_grouping"></eg-grid-field>
+ <eg-grid-field label="[% l('Subscription ID') %]" path="stream.distribution.subscription.id"></eg-grid-field>
+ <eg-grid-field label="[% l('Distribution ID') %]" path="stream.distribution.id"></eg-grid-field>
+ <eg-grid-field label="[% l('Stream ID') %]" path="stream.id"></eg-grid-field>
+ <eg-grid-field label="[% l('Item ID') %]" path="id"></eg-grid-field>
+ </eg-grid>
+</div>
+
--- /dev/null
+<select ng-model="ngModel">
+ <option value="99">[% l('Last') %]</option>
+ <option value="98">[% l('Next to Last') %]</option>
+ <option value="97">[% l('Third to Last') %]</option>
+ <option value="00">[% l('Every') %]</option>
+ <option value="01">[% l('First') %]</option>
+ <option value="02">[% l('Second') %]</option>
+ <option value="03">[% l('Third') %]</option>
+ <option value="04">[% l('Fourth') %]</option>
+ <option value="05">[% l('Fifth') %]</option>
+</select>
--- /dev/null
+<div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title">[% l('Edit MARC Holdings Record') %]</h4>
+ </div>
+ <div class="modal-body">
+ <eg-marc-edit-record dirty-flag="dirty_flag" marc-xml="args.marc_xml"
+ on-save="ok" in-place-mode="true" record-type="sre" save-label="[% l('Save') %]" />
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</div>
--- /dev/null
+<!--
+ MFHD creation dialog
+-->
+<div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title alert alert-info">Create new MFHD</h4>
+ </div>
+ <div class="modal-body">
+ <label for="mfhd_lib_selector">
+ [% l('Select a library') %]
+ </label>
+ <eg-org-selector id="mfhd_lib_selector"
+ selected="mfhd_lib">
+ </eg-org-selector>
+ </div>
+ <div class="modal-footer">
+ [% dialog_footer %]
+ <input type="submit" class="btn btn-primary"
+ ng-click="ok()" value="[% l('Create') %]"/>
+ <button class="btn btn-warning"
+ ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</div>
--- /dev/null
+<!--
+ Org selection interstitial
+-->
+<div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title alert alert-info">{{ title || '[% l('Select library') %]'}}</h4>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-md-12">
+ <eg-org-selector sticky-setting="{{rememberMe}}" selected="ws_ou" focus-me="focus"></eg-org-selector>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <input type="submit" class="btn btn-primary"
+ ng-click="ok()" value="[% l('OK/Continue') %]"/>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</div>
--- /dev/null
+<!--
+ Org selection interstitial
+-->
+<div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title alert alert-info">{{ title || '[% l('Select subscription') %]'}}</h4>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-md-12">
+ <eg-sub-selector bib-id="record_id" ssub-id="ssubId"></eg-sub-selector>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <input type="submit" class="btn btn-primary"
+ ng-click="ok()" value="[% l('OK/Continue') %]"/>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</div>
this._init.apply(this, arguments);
}
+function page_init() {
+ list_renderer = new ListRenderer(xulG.routing_list_data);
+ list_renderer.render().print();
+}
+
openils.Util.addOnLoad(
function() {
- if (!xulG) {
- alert(
- "This interface is not designed for use outside " +
- "the staff client." /* XXX i18n */
- );
- } else {
- list_renderer = new ListRenderer(xulG.routing_list_data);
- list_renderer.render().print();
- }
+ // assume we're NOT in the web staff client if we have xulG
+ if (typeof xulG !== 'undefined') return page_init();
}
);
--- /dev/null
+angular.module('egSerialsAdmin',
+ ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
+
+.config(['$routeProvider','$locationProvider','$compileProvider',
+ function($routeProvider , $locationProvider , $compileProvider) {
+
+ $locationProvider.html5Mode(true);
+ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/);
+ var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+ $routeProvider.when('/admin/serials/templates', {
+ templateUrl: './admin/serials/t_templates',
+ controller: 'TemplatesCtrl',
+ resolve : resolver
+ });
+
+ // default page
+ $routeProvider.otherwise({
+ templateUrl : './admin/serials/t_splash',
+ resolve : resolver
+ });
+}])
+
+// cheating
+.factory("sharedScope",function(){
+ return {};
+})
+
+.factory('templateSvc',
+ ['egCore','$q','$uibModal','ngToast',
+function(egCore , $q , $uibModal , ngToast ) {
+
+ var service = {
+ };
+
+ service.create_or_edit_template = function(id,ou,cb) {
+ $uibModal.open({
+ template: '<eg-serials-template template_id="' + id + '" owning_lib="' + ou + '"></eg-serials-template>',
+ controller:
+ ['sharedScope','$uibModalInstance',
+ function(sharedScope , $uibModalInstance ) {
+ sharedScope.close_modal = function(count) { $uibModalInstance.close({}) }
+ }],
+ windowClass: 'app-modal-window',
+ backdrop: 'static',
+ keyboard: false
+ }).result.then(
+ function(args) {
+ if (cb) { cb(); }
+ }
+ );
+ }
+
+ service.delete_template = function(id,cb) {
+ return egCore.pcrud.search('act',
+ {id : id},
+ null, {atomic : true}
+ ).then(function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) { console.log(evt); }
+ if (!evt && resp && resp.length > 0) {
+ return resp[0];
+ }
+ }).then(function(resp) {
+ resp.isdeleted(true); // needed?
+ return egCore.pcrud.remove(resp);
+ }).then(
+ function(resp) {
+ console.log(resp);
+ ngToast.success(egCore.strings.SERIALS_TEMPLATE_SUCCESS_DELETE);
+ },function(resp) {
+ console.log(resp);
+ ngToast.danger(egCore.strings.SERIALS_TEMPLATE_FAIL_DELETE);
+ }
+ ).finally(function() {
+ if (cb) { cb(); }
+ });
+ }
+
+ return service;
+}])
+
+.factory('itemSvc',
+ ['egCore','$q',
+function(egCore , $q) {
+
+ var service = {
+ };
+
+ service.get_locations = function(orgs) {
+ return egCore.pcrud.search('acpl',
+ {
+ owning_lib : orgs,
+ deleted : 'f'
+ },
+ {
+ flesh : 1,
+ flesh_fields : {
+ 'acpl' : ['owning_lib']
+ },
+ order_by : { acpl : 'name' }
+ }, {atomic : true}
+ );
+ };
+
+ service.get_statuses = function() {
+ if (egCore.env.ccs)
+ return $q.when(egCore.env.ccs.list);
+
+ return egCore.pcrud.retrieveAll('ccs', {order_by : { ccs : 'name' }}, {atomic : true}).then(
+ function(list) {
+ egCore.env.absorbList(list, 'ccs');
+ return list;
+ }
+ );
+
+ };
+
+ service.get_circ_mods = function() {
+ if (egCore.env.ccm)
+ return $q.when(egCore.env.ccm.list);
+
+ return egCore.pcrud.retrieveAll('ccm', {}, {atomic : true}).then(
+ function(list) {
+ egCore.env.absorbList(list, 'ccm');
+ return list;
+ }
+ );
+
+ };
+
+ service.get_circ_types = function() {
+ if (egCore.env.citm)
+ return $q.when(egCore.env.citm.list);
+
+ return egCore.pcrud.retrieveAll('citm', {}, {atomic : true}).then(
+ function(list) {
+ egCore.env.absorbList(list, 'citm');
+ return list;
+ }
+ );
+
+ };
+
+ service.get_age_protects = function() {
+ if (egCore.env.crahp)
+ return $q.when(egCore.env.crahp.list);
+
+ return egCore.pcrud.retrieveAll('crahp', {}, {atomic : true}).then(
+ function(list) {
+ egCore.env.absorbList(list, 'crahp');
+ return list;
+ }
+ );
+
+ };
+
+ service.get_floating_groups = function() {
+ if (egCore.env.cfg)
+ return $q.when(egCore.env.cfg.list);
+
+ return egCore.pcrud.retrieveAll('cfg', {}, {atomic : true}).then(
+ function(list) {
+ egCore.env.absorbList(list, 'cfg');
+ return list;
+ }
+ );
+
+ };
+
+ service.bmp_parts = {};
+ service.get_parts = function(rec) {
+ if (service.bmp_parts[rec])
+ return $q.when(service.bmp_parts[rec]);
+
+ return egCore.pcrud.search('bmp',
+ {record : rec, deleted : 'f'},
+ null, {atomic : true}
+ ).then(function(list) {
+ service.bmp_parts[rec] = list;
+ return list;
+ });
+
+ };
+
+ return service;
+}])
+
+.controller('TemplatesCtrl',
+ ['$scope','$q','$window','$routeParams','$location','$timeout','egCore','egNet','itemSvc','templateSvc',
+ 'egGridDataProvider',
+function($scope , $q , $window , $routeParams , $location , $timeout , egCore , egNet , itemSvc , templateSvc ,
+ egGridDataProvider ) {
+
+ function current_query() {
+ var filter = {
+ 'owning_lib' : egCore.org.descendants($scope.context_ou.id(), true)
+ };
+ return filter;
+ }
+
+ function refresh_page() {
+ $scope.grid_controls.setQuery(current_query());
+ }
+
+ $scope.grid_actions = {
+ create_template : function() {
+ templateSvc.create_or_edit_template(null,$scope.context_ou.id(),refresh_page);
+ },
+ edit_template : function(items) {
+ templateSvc.create_or_edit_template(items[0].id,$scope.context_ou.id(),refresh_page);
+ },
+ delete_template : function(items) {
+ var promises = [];
+ angular.forEach(items,function(item) {
+ promises.push(templateSvc.delete_template(item.id));
+ });
+ $q.all(promises).then(function() {
+ refresh_page();
+ });
+ }
+ }
+ $scope.grid_controls = {
+ activateItem : function(item) {
+ templateSvc.create_or_edit_template(item.id,$scope.context_ou.id(),refresh_page);
+ },
+ setQuery : function(x) { return x || current_query(); },
+ setSort : function() { return ['name','id'] }
+ }
+
+ $scope.need_one_selected = function() {
+ var items = $scope.grid_controls.selectedItems();
+ if (items.length == 1) return false;
+ return true;
+ };
+
+ // called after any egGridActions action occurs
+ $scope.grid_actions.refresh = refresh_page;
+
+ // re-draw the grid when user changes the org selector
+ $scope.context_ou = egCore.org.get(egCore.auth.user().ws_ou());
+ $scope.$watch('context_ou', function(newVal, oldVal) {
+ if (newVal && newVal != oldVal)
+ refresh_page();
+ });
+
+ refresh_page();
+
+}])
+
+.directive("egSerialsTemplate", function () {
+ return {
+ restrict: 'E',
+ replace: true,
+ template: '<div ng-include="'+"'/eg/staff/admin/serials/t_attr_edit'"+'"></div>',
+ scope: {
+ templateId: '=',
+ owningLib: '='
+ },
+ controller : ['$scope','$q','$window','itemSvc','egCore','ngToast','sharedScope',
+ function ( $scope , $q , $window , itemSvc , egCore , ngToast , sharedScope ) {
+
+ $scope.close_modal = function() {
+ if ($scope.dirty && !window.confirm(egCore.strings.CONFIRM_DIRTY_EXIT)) {
+ return;
+ }
+ //console.log('unsetting dirty for close_modal');
+ $scope.dirty = false;
+ sharedScope.close_modal();
+ };
+
+ $scope.defaults = { // If defaults are not set at all, allow everything
+ attributes : {
+ status : true,
+ loan_duration : true,
+ fine_level : true,
+ alerts : true,
+ deposit : true,
+ deposit_amount : true,
+ opac_visible : true,
+ price : true,
+ circulate : true,
+ mint_condition : true,
+ circ_lib : true,
+ ref : true,
+ circ_modifier : true,
+ circ_as_type : true,
+ location : true,
+ holdable : true,
+ age_protect : true,
+ floating : true
+ }
+ };
+
+ $scope.fetchDefaults = function () {
+ egCore.hatch.getItem('serials.copy.defaults').then(function(t) {
+ if (t) {
+ $scope.defaults = t;
+ }
+ });
+ }
+ $scope.fetchDefaults();
+
+ //console.log('unsetting dirty by default');
+ $scope.dirty = false;
+ $scope.$watch('dirty',
+ function(newVal, oldVal) {
+ //console.log('watching dirty');
+ //console.log('...oldVal',oldVal);
+ //console.log('...newVal',newVal);
+ //console.log('...fetching',$scope.fetching);
+ if (newVal && $scope.fetching) {
+ // KLUDGY
+ // so after fetchTemplate -> applyTemplate
+ // the working watches will fire and set
+ // dirty to true. We'll undo that at this
+ // point.
+ //console.log('unsetting dirty via kludge');
+ $scope.fetching = false;
+ $scope.dirty = false;
+ newVal = false;
+ }
+ if (newVal && newVal != oldVal) {
+ $($window).on('beforeunload.template', function(){
+ return 'There is unsaved template data!'
+ });
+ } else {
+ $($window).off('beforeunload.template');
+ }
+ }
+ );
+
+ $scope.applyTemplate = function() {
+ //console.log('applying...');
+ angular.forEach($scope.hashed_template, function (v,k) {
+ //console.log(k,v);
+ if (k == 'circ_lib') {
+ $scope.working[k] = egCore.org.get(v);
+ } else if (!angular.isObject(v)) {
+ $scope.working[k] = angular.copy(v);
+ } else {
+ angular.forEach(v, function (sv,sk) {
+ if (!(k in $scope.working))
+ $scope.working[k] = {};
+ $scope.working[k][sk] = angular.copy(sv);
+ });
+ }
+ });
+ //console.log('unsetting dirty via applyTemplate');
+ $scope.dirty = false;
+ }
+
+ $scope.fetching = false;
+ $scope.fetchTemplate = function () {
+ $scope.fetching = true;
+ return egCore.pcrud.search('act',
+ {id : $scope.templateId},
+ null, {atomic : true}
+ ).then(function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) { console.log(evt); }
+ if (!evt && resp && resp.length > 0) {
+ $scope.fm_template = resp[0];
+ $scope.hashed_template = egCore.idl.toHash(resp[0]);
+ $scope.applyTemplate();
+ } else {
+ console.log('new template');
+ }
+ });
+ }
+
+ $scope.saveTemplate = function() {
+ var tmpl = {};
+
+ angular.forEach($scope.working, function (v,k) {
+ if (angular.isObject(v)) { // we'll use the pkey
+ if (v.id) v = v.id();
+ else if (v.code) v = v.code();
+ }
+
+ tmpl[k] = v;
+ });
+
+ $scope.hashed_template = tmpl;
+
+ var act_obj = $scope.fm_template || new egCore.idl.act() ;
+ //console.log('consuming...');
+ angular.forEach($scope.hashed_template, function (v,k) {
+ //console.log(k,v);
+ if (typeof act_obj[k] == 'function') {
+ act_obj[k](v);
+ } else {
+ console.log('something wrong here',k,act_obj[k]);
+ }
+ });
+ if ($scope.fm_template) {
+ console.log('edit');
+ act_obj.ischanged('t');
+ act_obj.editor( egCore.auth.user().id() );
+ act_obj.edit_date( new Date() );
+ } else {
+ console.log('create');
+ act_obj.isnew('t');
+ act_obj.creator( egCore.auth.user().id() );
+ act_obj.owning_lib( $scope.owningLib );
+ act_obj.create_date( new Date() );
+ }
+ var some_failure = false;
+ var some_success = false;
+ egCore.net.request(
+ 'open-ils.cat', // worth replacing with pcrud?
+ 'open-ils.cat.asset.copy_template.create_or_update',
+ egCore.auth.token(),
+ act_obj
+ ).then(
+ function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) { // any way to just throw or return this to the error handler?
+ console.log('failure',resp);
+ some_failure = true;
+ ngToast.danger(egCore.strings.SERIALS_TEMPLATE_FAIL_SAVE);
+ } else {
+ console.log('success',resp);
+ some_success = true;
+ ngToast.success(egCore.strings.SERIALS_TEMPLATE_SUCCESS_SAVE);
+ }
+ },
+ function(resp) {
+ console.log('failure',resp);
+ some_failure = true;
+ ngToast.danger(egCore.strings.SERIALS_TEMPLATE_FAIL_SAVE);
+ }
+ ).then(function(){
+ if (some_success && !some_failure) {
+ //console.log('unsetting dirty for save');
+ $scope.dirty = false;
+ $scope.close_modal();
+ }
+ });
+ }
+
+ $scope.hashed_template = {};
+ $scope.imported_template = { data : '' };
+ $scope.fetchTemplate();
+
+ // FIXME - leaving this for now
+ $scope.$watch('imported_template.data', function(newVal, oldVal) {
+ if (newVal && newVal != oldVal) {
+ try {
+ var newTemplate = JSON.parse(newVal);
+ if (!Object.keys(newTemplate).length) return;
+ $scope.hashed_template = newTemplate;
+ } catch (E) {
+ console.log('tried to import an invalid serials template file');
+ }
+ }
+ });
+
+ $scope.orgById = function (id) { return egCore.org.get(id) }
+ $scope.statusById = function (id) {
+ return $scope.status_list.filter( function (s) { return s.id() == id } )[0];
+ }
+ $scope.locationById = function (id) {
+ return $scope.location_cache[''+id];
+ }
+
+ createSimpleUpdateWatcher = function (field) {
+ $scope.$watch('working.' + field, function () {
+ var newval = $scope.working[field];
+
+ if (typeof newval != 'undefined') {
+ //console.log('setting dirty for field',field);
+ $scope.dirty = true;
+ if (angular.isObject(newval)) { // we'll use the pkey
+ if (newval.id) $scope.working[field] = newval.id();
+ else if (newval.code) $scope.working[field] = newval.code();
+ }
+
+ if (""+newval == "" || newval == null) {
+ $scope.working[field] = undefined;
+ }
+
+ }
+ });
+ }
+
+ $scope.clearWorking = function () {
+ angular.forEach($scope.working, function (v,k,o) {
+ if (!angular.isObject(v)) {
+ if (typeof v != 'undefined')
+ $scope.working[k] = undefined;
+ } else if (k != 'circ_lib') {
+ angular.forEach(v, function (sv,sk) {
+ $scope.working[k][sk] = undefined;
+ });
+ }
+ });
+ $scope.working.circ_lib = undefined; // special
+ $scope.working.loan_duration = 2;
+ $scope.working.fine_level = 2;
+ //console.log('unsetting dirty for clearWorking');
+ $scope.dirty = false;
+ }
+
+ $scope.working = {
+ loan_duration : 2,
+ fine_level : 2
+ };
+ $scope.location_orgs = [];
+ $scope.location_cache = {};
+
+ $scope.i18n = egCore.i18n;
+ $scope.location_list = [];
+ itemSvc.get_locations(
+ egCore.org.fullPath( egCore.auth.user().ws_ou(), true )
+ ).then(function(list){
+ $scope.location_list = list;
+ });
+ createSimpleUpdateWatcher('location');
+
+ $scope.status_list = [];
+ itemSvc.get_statuses().then(function(list){
+ $scope.status_list = list;
+ });
+ createSimpleUpdateWatcher('status');
+
+ $scope.circ_modifier_list = [];
+ itemSvc.get_circ_mods().then(function(list){
+ $scope.circ_modifier_list = list;
+ });
+ createSimpleUpdateWatcher('circ_modifier');
+
+ $scope.circ_type_list = [];
+ itemSvc.get_circ_types().then(function(list){
+ $scope.circ_type_list = list;
+ });
+ createSimpleUpdateWatcher('circ_as_type');
+
+ $scope.age_protect_list = [];
+ itemSvc.get_age_protects().then(function(list){
+ $scope.age_protect_list = list;
+ });
+ createSimpleUpdateWatcher('age_protect');
+
+ createSimpleUpdateWatcher('circulate');
+ createSimpleUpdateWatcher('holdable');
+
+ $scope.loan_duration_options = [
+ {
+ v: function(){return 1;},
+ l: function(){return egCore.strings.LOAN_DURATION_SHORT;}
+ },
+ {
+ v: function(){return 2;},
+ l: function(){return egCore.strings.LOAN_DURATION_NORMAL;}
+ },
+ {
+ v: function(){return 3;},
+ l: function(){return egCore.strings.LOAN_DURATION_EXTENDED;}
+ }
+ ];
+ createSimpleUpdateWatcher('loan_duration');
+
+ $scope.fine_level_options = [
+ {
+ v: function(){return 1;},
+ l: function(){return egCore.strings.FINE_LEVEL_LOW;}
+ },
+ {
+ v: function(){return 2;},
+ l: function(){return egCore.strings.FINE_LEVEL_NORMAL;}
+ },
+ {
+ v: function(){return 3;},
+ l: function(){return egCore.strings.FINE_LEVEL_HIGH;}
+ }
+ ];
+ createSimpleUpdateWatcher('fine_level');
+
+ createSimpleUpdateWatcher('name');
+ createSimpleUpdateWatcher('price');
+ createSimpleUpdateWatcher('deposit');
+ createSimpleUpdateWatcher('deposit_amount');
+ createSimpleUpdateWatcher('mint_condition');
+ createSimpleUpdateWatcher('opac_visible');
+ createSimpleUpdateWatcher('ref');
+ }
+ ]
+ }
+})
+
+
--- /dev/null
+angular.module('egAdminConfig',
+ ['ngRoute','ui.bootstrap','egCoreMod','egUiMod','egGridMod','egFmRecordEditorMod','egSerialsMod','egSerialsAppDep'])
+
+.controller('PatternTemplate',
+ ['$scope','$q','$timeout','$location','$window','$uibModal','egCore','egGridDataProvider',
+ 'egConfirmDialog','ngToast',
+function($scope , $q , $timeout , $location , $window , $uibModal , egCore , egGridDataProvider ,
+ egConfirmDialog , ngToast) {
+
+ egCore.startup.go(); // standalone mode requires manual startup
+
+ $scope.new_record = function() {
+ spawn_editor();
+ }
+
+ $scope.need_one_selected = function() {
+ var items = $scope.gridControls.selectedItems();
+ if (items.length == 1) return false;
+ return true;
+ };
+
+ $scope.edit_record = function(items) {
+ if (items.length != 1) return;
+ spawn_editor(items[0].id);
+ }
+
+ spawn_editor = function(id) {
+ var templ;
+ if (arguments.length == 1) {
+ templ = '<eg-edit-fm-record idl-class="spt" mode="update" record-id="id" on-save="ok" on-cancel="cancel" custom-field-templates="customFieldTemplates"></eg-edit-fm-record>';
+ } else {
+ templ = '<eg-edit-fm-record idl-class="spt" mode="create" on-save="ok" on-cancel="cancel" custom-field-templates="customFieldTemplates" org-default-allowed="owning_lib"></eg-edit-fm-record>';
+ }
+ gridControls = $scope.gridControls;
+ $uibModal.open({
+ template : templ,
+ controller : [
+ '$scope', '$uibModalInstance',
+ function($scope , $uibModalInstance) {
+ $scope.id = id;
+
+ $scope.openPatternEditorDialog = function(pred) {
+ $uibModal.open({
+ templateUrl: './serials/t_pattern_editor_dialog',
+ size: 'lg',
+ windowClass: 'eg-wide-modal',
+ backdrop: 'static',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ $scope.focusMe = true;
+ $scope.showShare = false;
+ $scope.patternCode = pred.pattern_code;
+ $scope.ok = function(patternCode) { $uibModalInstance.close(patternCode) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ }]
+ }).result.then(function (patternCode) {
+ if (pred.pattern_code !== patternCode) {
+ pred.pattern_code = patternCode;
+ }
+ });
+ }
+
+ $scope.customFieldTemplates = {
+ share_depth : {
+ template : '<eg-share-depth-selector ng-model="rec_flat[field.name]">'
+ },
+ pattern_code : {
+ handlers : {
+ openPatternEditorDialog : $scope.openPatternEditorDialog
+ },
+ template : '<button class="btn btn-default" ng-click="field.handlers.openPatternEditorDialog(rec_flat)">Pattern Wizard</button>' + // FIXME i18n
+ // using a required hidden input as a way to ensure that
+ // the pattern wizard has been used
+ '<input type="hidden" required ng-model="rec_flat[field.name]">'
+ }
+ }
+
+ $scope.ok = function($event) {
+ $uibModalInstance.close();
+ gridControls.refresh();
+ }
+
+ $scope.cancel = function($event) {
+ $uibModalInstance.dismiss();
+ }
+ }
+ ]
+ });
+ }
+
+ $scope.delete_selected = function(selected) {
+ if (!selected || !selected.length) return;
+ var ids = selected.map(function(rec) { return rec.id });
+
+ egConfirmDialog.open(
+ egCore.strings.EG_CONFIRM_DELETE_PATTERN_TEMPLATE_TITLE,
+ egCore.strings.EG_CONFIRM_DELETE_PATTERN_TEMPLATE_BODY,
+ { count : ids.length }
+ ).result.then(function() {
+ var promises = [];
+ var list = [];
+ angular.forEach(selected, function(rec) {
+ promises.push(
+ egCore.pcrud.retrieve('spt', rec.id).then(function(r) {
+ list.push(r);
+ })
+ );
+ })
+ $q.all(promises).then(function() {
+ egCore.pcrud.remove(list).then(function() {
+ ngToast.success(egCore.strings.PATTERN_TEMPLATE_SUCCESS_DELETE);
+ $scope.gridControls.refresh();
+ },
+ function() {
+ ngToast.success(egCore.strings.PATTERN_TEMPLATE_FAIL_DELETE);
+ });
+ });
+ });
+ }
+
+ function generateQuery() {
+ return {
+ 'id' : { '!=' : null },
+ }
+ }
+
+ $scope.gridControls = {
+ setQuery : function() {
+ return generateQuery();
+ },
+ setSort : function() {
+ return ['owning_lib.name','name'];
+ }
+ }
+}])
*
*/
-angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','ngLocationUpdate','egCoreMod','egGridMod', 'egMarcMod', 'egUserMod', 'egHoldingsMod', 'ngToast','egPatronSearchMod'])
+angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','ngLocationUpdate','egCoreMod','egGridMod', 'egMarcMod', 'egUserMod', 'egHoldingsMod', 'ngToast','egPatronSearchMod',
+'egSerialsMod','egSerialsAppDep'])
.config(['ngToastProvider', function(ngToastProvider) {
ngToastProvider.configure({
.controller('CatalogCtrl',
['$scope','$routeParams','$location','$window','$q','egCore','egHolds','egCirc','egConfirmDialog','ngToast',
'egGridDataProvider','egHoldGridActions','egProgressDialog','$timeout','$uibModal','holdingsSvc','egUser','conjoinedSvc',
- '$cookies',
+ '$cookies','egSerialsCoreSvc',
function($scope , $routeParams , $location , $window , $q , egCore , egHolds , egCirc , egConfirmDialog , ngToast ,
egGridDataProvider , egHoldGridActions , egProgressDialog , $timeout , $uibModal , holdingsSvc , egUser , conjoinedSvc,
- $cookies
+ $cookies , egSerialsCoreSvc
) {
var holdingsSvcInst = new holdingsSvc();
$scope.current_voltransfer_target = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
$scope.current_conjoined_target = egCore.hatch.getLocalItem('eg.cat.marked_conjoined_record');
+ $scope.quickReceive = function () {
+ var list = [];
+ var next_per_stream = {};
+
+ var recId = $scope.record_id;
+ return $uibModal.open({
+ templateUrl: './share/t_subscription_select_dialog',
+ controller: ['$scope', '$uibModalInstance',
+ function($scope, $uibModalInstance) {
+
+ $scope.focus = true;
+ $scope.rememberMe = 'eg.serials.quickreceive.last_org';
+ $scope.record_id = recId;
+ $scope.ssubId = null;
+
+ $scope.ok = function() { $uibModalInstance.close($scope.ssubId) }
+ $scope.cancel = function() { $uibModalInstance.dismiss(); }
+ }
+ ]
+ }).result.then(function(ssubId) {
+ if (ssubId) {
+ var promises = [];
+ promises.push(egSerialsCoreSvc.fetchItemsForSub(ssubId,{status:'Expected'}).then(function(){
+ angular.forEach(egSerialsCoreSvc.itemTree, function (item) {
+ if (next_per_stream[item.stream().id()]) return;
+ if (item.status() == 'Expected') {
+ next_per_stream[item.stream().id()] = item;
+ list.push(egCore.idl.Clone(item));
+ }
+ });
+ }));
+
+ return $q.all(promises).then(function() {
+
+ if (!list.length) {
+ ngToast.warning(egCore.strings.SERIALS_NO_ITEMS);
+ return $q.reject();
+ }
+
+ return egSerialsCoreSvc.process_items(
+ 'receive',
+ $scope.record_id,
+ list,
+ true, // barcode
+ false,// bind
+ false, // print by default
+ function() { $scope.holdings_record_id_changed($scope.record_id) }
+ );
+ });
+ } else {
+ ngToast.warning(egCore.strings.SERIALS_NO_SUBS);
+ return $q.reject();
+ }
+ });
+ }
+
$scope.markConjoined = function () {
$scope.current_conjoined_target = $scope.record_id;
egCore.hatch.setLocalItem('eg.cat.marked_conjoined_record',$scope.record_id);
--- /dev/null
+angular.module('egSerialsApp', ['ui.bootstrap','ngRoute','egCoreMod','egGridMod','ngToast','egSerialsMod','egMfhdMod','egMarcMod','egSerialsAppDep']);
+angular.module('egSerialsAppDep', []);
+
+angular.module('egSerialsApp')
+.config(['ngToastProvider', function(ngToastProvider) {
+ ngToastProvider.configure({
+ verticalPosition: 'bottom',
+ animation: 'fade'
+ });
+}])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+ $locationProvider.html5Mode(true);
+ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+ var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+ $routeProvider.when('/serials/:bib_id', {
+ templateUrl: './serials/t_manage',
+ controller: 'ManageCtrl',
+ resolve : resolver
+ });
+
+ $routeProvider.when('/serials/:bib_id/:active_tab', {
+ templateUrl: './serials/t_manage',
+ controller: 'ManageCtrl',
+ resolve : resolver
+ });
+
+ $routeProvider.when('/serials/:bib_id/:active_tab/:subscription_id', {
+ templateUrl: './serials/t_manage',
+ controller: 'ManageCtrl',
+ resolve : resolver
+ });
+})
+
+.controller('ManageCtrl',
+ ['$scope','$routeParams','$location','egSerialsCoreSvc',
+function($scope , $routeParams , $location , egSerialsCoreSvc) {
+ $scope.bib_id = $routeParams.bib_id;
+ $scope.active_tab = $routeParams.active_tab ? $routeParams.active_tab : 'manage-subscriptions';
+ $scope.ssub = {id : null};
+ if ($routeParams.subscription_id) {
+ egSerialsCoreSvc.verify_subscription_id($scope.bib_id, $routeParams.subscription_id)
+ .then(function(verified) {
+ if (verified) {
+ $scope.ssub.id = $routeParams.subscription_id;
+ } else {
+ // subscription ID is no good, so drop it from the URL
+ $location.path('/serials/' + $scope.bib_id + '/' + $scope.active_tab);
+ }
+ });
+ }
+ $scope.$watch('ssub.id', function(newVal, oldVal) {
+ if (oldVal != newVal) {
+ $location.path('/serials/' + $scope.bib_id + '/' + $scope.active_tab +
+ '/' + $scope.ssub.id);
+ }
+ });
+ $scope.$watch('active_tab', function(newVal, oldVal) {
+ if (oldVal != newVal) {
+ var new_path = '/serials/' + $scope.bib_id + '/' + $scope.active_tab;
+ if ($scope.ssub.id && $scope.active_tab != 'manage-subscriptions') {
+ new_path += '/' + $scope.ssub.id;
+ }
+ $location.path(new_path);
+ }
+ });
+}])
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egItemManager', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ bibId : '=',
+ ssubId : '='
+ },
+ templateUrl: './serials/t_item_manager',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','$uibModal',
+function($scope , $q , egSerialsCoreSvc , egCore , $uibModal) {
+
+ egSerialsCoreSvc.fetch($scope.bibId);
+
+}]
+ }
+})
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egMfhdManager', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ bibId : '=',
+ },
+ templateUrl: './serials/t_mfhd_manager',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
+ '$uibModal','$timeout','egMfhdCreateDialog','egConfirmDialog',
+function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
+ $uibModal , $timeout , egMfhdCreateDialog , egConfirmDialog) {
+
+ function reload() {
+ egSerialsCoreSvc.fetch_mfhds($scope.bibId).then(function() {
+ $scope.mfhdGridDataProvider.refresh();
+ });
+ }
+ reload();
+
+ $scope.mfhdGridControls = {
+ activateItem : function (item) { } // TODO
+ };
+ $scope.mfhdGridDataProvider = egGridDataProvider.instance({
+ get : function(offset, count) {
+ return this.arrayNotifier(egSerialsCoreSvc.flatMfhdList, offset, count);
+ }
+ });
+ $scope.need_one_selected = function() {
+ var items = $scope.mfhdGridControls.selectedItems();
+ if (items.length == 1) return false;
+ return true;
+ };
+
+ $scope.createMfhd = function() {
+ egMfhdCreateDialog.open($scope.bibId).result.then(function() {
+ reload();
+ });
+ };
+
+ $scope.edit_mfhd = function() {
+ var items = $scope.mfhdGridControls.selectedItems();
+ if (items.length != 1) return;
+ var args = {
+ 'marc_xml' : items[0].marc_xml
+ }
+ $uibModal.open({
+ templateUrl: './share/t_edit_mfhd',
+ size: 'lg',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ $scope.focusMe = true;
+ $scope.args = args;
+ $scope.dirty_flag = false;
+ $scope.ok = function() { $uibModalInstance.close($scope.args) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ }]
+ }).result.then(function (args) {
+ egCore.pcrud.retrieve('sre', items[0].id).then(function(sre) {
+ sre.marc(args.marc_xml);
+ egCore.pcrud.update(sre).then(function() {
+ reload();
+ });
+ });
+ });
+ };
+
+ $scope.delete_mfhds = function() {
+ var items = $scope.mfhdGridControls.selectedItems();
+ if (items.length <= 0) return;
+
+ egConfirmDialog.open(
+ egCore.strings.CONFIRM_DELETE_MFHDS,
+ egCore.strings.CONFIRM_DELETE_MFHDS_MESSAGE,
+ {items : items.length}
+ ).result.then(function () {
+ var promises = [];
+ angular.forEach(items, function(mfhd) {
+ var promise = $q.defer();
+ promises.push(promise.promise);
+ egCore.pcrud.retrieve('sre', mfhd.id).then(function(sre) {
+ egCore.pcrud.remove(sre).then(function() {
+ promise.resolve();
+ });
+ })
+ });
+ $q.all(promises).then(function() {
+ reload();
+ });
+ });
+ }
+}]
+ }
+})
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egPredictionManager', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ bibId : '=',
+ ssubId : '='
+ },
+ templateUrl: './serials/t_prediction_manager',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
+ '$uibModal','$timeout','$location','egConfirmDialog','ngToast',
+function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
+ $uibModal , $timeout , $location , egConfirmDialog , ngToast) {
+
+ $scope.has_pattern_to_import = false;
+ $scope.forms = [];
+ egSerialsCoreSvc.fetch($scope.bibId).then(function() {
+ reload($scope.ssubId);
+ egSerialsCoreSvc.fetch_patterns_from_bibs_mfhds($scope.bibId).then(function() {
+ if (egSerialsCoreSvc.potentialPatternList.length > 0) {
+ $scope.has_pattern_to_import = true;
+ }
+ });
+ });
+
+ function reload(ssubId) {
+ if (!ssubId) return;
+ var ssub = egSerialsCoreSvc.get_ssub(ssubId);
+ $scope.predictions = egCore.idl.toTypedHash(ssub.scaps());
+ angular.forEach($scope.predictions, function(pred) {
+ pred._can_edit_or_delete = false;
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.caption_and_pattern.safe_delete.dry_run',
+ egCore.auth.token(),
+ pred.id
+ ).then(function(result) {
+ if (result == 1) pred._can_edit_or_delete = true;
+ });
+ });
+ egSerialsCoreSvc.fetch_spt().then(function() {
+ $scope.pattern_templates = egCore.idl.toTypedHash(egSerialsCoreSvc.sptList);
+ $scope.active_pattern_template = { id : null };
+ if ($scope.pattern_templates.length > 0) {
+ $scope.active_pattern_template.id = $scope.pattern_templates[0].id;
+ }
+ });
+ }
+
+ $scope.createScap = function(pred) {
+ var scap = egCore.idl.fromTypedHash(pred);
+ egCore.pcrud.create(scap).then(function() {
+ // completely reset the model in order to reset the
+ // forms; causes a blink, alas
+ $scope.predictions = [];
+ $scope.new_prediction = null;
+ egSerialsCoreSvc.fetch($scope.bibId).then(function() {
+ reload($scope.ssubId);
+ });
+ });
+ }
+ $scope.updateScap = function(pred) {
+ var scap = egCore.idl.fromTypedHash(pred);
+ egCore.pcrud.update(scap).then(function() {
+ // completely reset the model in order to reset the
+ // forms; causes a blink, alas
+ $scope.predictions = [];
+ egSerialsCoreSvc.fetch($scope.bibId).then(function() {
+ reload($scope.ssubId);
+ });
+ });
+ }
+ $scope.deleteScap = function(pred) {
+ var scap = egCore.idl.fromTypedHash(pred);
+ egConfirmDialog.open(
+ egCore.strings.CONFIRM_DELETE_SCAP,
+ egCore.strings.CONFIRM_DELETE_SCAP_MESSAGE,
+ {}
+ ).result.then(function () {
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.caption_and_pattern.safe_delete',
+ egCore.auth.token(),
+ scap.id()
+ ).then(function(resp){
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_SCAP_FAIL_DELETE + ' : ' + evt.desc);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_SCAP_SUCCESS_DELETE);
+ }
+
+ $scope.predictions = [];
+ egSerialsCoreSvc.fetch($scope.bibId).then(function() {
+ reload($scope.ssubId);
+ });
+ })
+ });
+ }
+ $scope.cancelNewScap = function() {
+ $scope.new_prediction = null;
+ }
+ $scope.startNewScap = function() {
+ $scope.new_prediction = egCore.idl.toTypedHash(new egCore.idl.scap());
+ $scope.new_prediction.type = 'basic';
+ $scope.new_prediction.active = true;
+ $scope.new_prediction.create_date = new Date();
+ $scope.new_prediction.subscription = $scope.ssubId;
+ $scope.new_prediction.pattern_code = null;
+ }
+
+ $scope.importScapFromBibRecord = function() {
+ $uibModal.open({
+ templateUrl: './serials/t_select_pattern_dialog',
+ size: 'md',
+ backdrop: 'static',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ $scope.focusMe = true;
+ $scope.potentials = egSerialsCoreSvc.potentialPatternList.slice();
+ $scope.ok = function(patternCode) { $uibModalInstance.close($scope.potentials) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ }]
+ }).result.then(function (potentials) {
+ var marc = [];
+ angular.forEach(potentials, function(pot) {
+ if (pot.selected) {
+ marc.push(pot.marc);
+ }
+ });
+ if (marc.length == 0) return;
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.caption_and_pattern.create_from_records',
+ egCore.auth.token(),
+ $scope.ssubId,
+ marc
+ ).then(function() {
+ egSerialsCoreSvc.fetch($scope.bibId).then(function() {
+ reload($scope.ssubId);
+ });
+ });
+ });
+ }
+
+ $scope.importScapFromSpt = function() {
+ $scope.new_prediction = egCore.idl.toTypedHash(new egCore.idl.scap());
+ $scope.new_prediction.type = 'basic';
+ $scope.new_prediction.active = true;
+ $scope.new_prediction.create_date = new Date();
+ $scope.new_prediction.subscription = $scope.ssubId;
+ for (var i = 0; i < $scope.pattern_templates.length; i++) {
+ if ($scope.pattern_templates[i].id == $scope.active_pattern_template.id) {
+ $scope.new_prediction.pattern_code = $scope.pattern_templates[i].pattern_code;
+ break;
+ }
+ }
+ // Mark form dirty because, when it's created from a template,
+ // it can be immediately saved if the user so chooses. The
+ // $watch() allows this to happen after the form is bound
+ // is bound to the scope.
+ $scope.$watch('forms.newpredform', function(form) {
+ if (form) form.$setDirty();
+ });
+ }
+
+ $scope.openPatternEditorDialog = function(pred, form, viewOnly) {
+ $uibModal.open({
+ templateUrl: './serials/t_pattern_editor_dialog',
+ size: 'lg',
+ windowClass: 'eg-wide-modal',
+ backdrop: 'static',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ $scope.viewOnly = viewOnly;
+ $scope.focusMe = true;
+ $scope.patternCode = pred.pattern_code;
+ $scope.ok = function(patternCode) { $uibModalInstance.close(patternCode) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ }]
+ }).result.then(function (patternCode) {
+ if (pred.pattern_code !== patternCode) {
+ pred.pattern_code = patternCode;
+ form.$setDirty();
+ }
+ });
+ }
+
+ $scope.add_issuances = function() {
+ return egSerialsCoreSvc.fetchItemsForSub($scope.ssubId).then(function() {
+ egSerialsCoreSvc.add_issuances($scope.ssubId).then(function() {
+ $location.path('/serials/' + $scope.bibId + '/issues/' +
+ $scope.ssubId);
+ });
+ });
+ }
+
+}]
+ }
+})
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egPredictionWizard', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ patternCode : '=',
+ onSave : '=',
+ showShare : '=',
+ viewOnly : '='
+ },
+ templateUrl: './serials/t_prediction_wizard',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
+function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider) {
+
+ $scope.tab = { active : 0 };
+ if (angular.isUndefined($scope.showShare)) {
+ $scope.showShare = true;
+ }
+ if (angular.isUndefined($scope.viewOnly)) {
+ $scope.viewOnly = false;
+ }
+
+ // for use by ng-value
+ $scope.True = true;
+ $scope.False = false;
+
+ // class for MARC21 serial prediction pattern
+ // TODO move elsewhere
+ function PredictionPattern(patternCode) {
+ var self = this;
+ this.use_enum = false;
+ this.use_alt_enum = false;
+ this.use_chron = false;
+ this.use_alt_chron = false;
+ this.use_calendar_changes = false;
+ this.calendar_change = [];
+ this.compress_expand = '3';
+ this.caption_evaluation = '0';
+ this.enum_levels = [];
+ this.alt_enum_levels = [];
+ this.chron_levels = [];
+ this.alt_chron_levels = [{ caption : null, display_caption: false }];
+ this.frequency_type = 'preset';
+ this.use_regularity = false;
+ this.regularity = [];
+
+ var nr_sf_map = {
+ '8' : 'link',
+ 'n' : 'note',
+ 'p' : 'pieces_per_issuance',
+ 'w' : 'frequency',
+ 't' : 'copy_caption'
+ }
+ var enum_level_map = {
+ 'a' : 0,
+ 'b' : 1,
+ 'c' : 2,
+ 'd' : 3,
+ 'e' : 4,
+ 'f' : 5
+ }
+ var alt_enum_level_map = {
+ 'g' : 0,
+ 'h' : 1
+ }
+ var chron_level_map = {
+ 'i' : 0,
+ 'j' : 1,
+ 'k' : 2,
+ 'l' : 3
+ }
+ var alt_chron_level_map = {
+ 'm' : 0
+ }
+
+ var curr_enum_level = -1;
+ var curr_alt_enum_level = -1;
+ var curr_chron_level = -1;
+ var curr_alt_chron_level = -1;
+ if (patternCode && patternCode.length > 2 && (patternCode.length % 2 == 0)) {
+ // set indicator values
+ this.compress_expand = patternCode[0];
+ this.caption_evaluation = patternCode[1];
+ for (var i = 2; i < patternCode.length; i += 2) {
+ var sf = patternCode[i];
+ var value = patternCode[i + 1];
+ if (sf in nr_sf_map) {
+ this[nr_sf_map[sf]] = value;
+ continue;
+ }
+ if (sf in enum_level_map) {
+ this.use_enum = true;
+ curr_enum_level = enum_level_map[sf];
+ this.enum_levels[curr_enum_level] = {
+ caption : value,
+ restart : false
+ }
+ continue;
+ }
+ if (sf in alt_enum_level_map) {
+ this.use_enum = true;
+ this.use_alt_enum = true;
+ curr_enum_level = -1;
+ curr_alt_enum_level = alt_enum_level_map[sf];
+ this.alt_enum_levels[curr_alt_enum_level] = {
+ caption : value,
+ restart : false
+ }
+ continue;
+ }
+ if (sf in chron_level_map) {
+ this.use_chron = true;
+ curr_chron_level = chron_level_map[sf];
+ var chron = {};
+ if (value.match(/^\(.*\)$/)) {
+ chron.display_caption = false;
+ chron.caption = value.replace(/^\(/, '').replace(/\)$/, '');
+ } else {
+ chron.display_caption = true;
+ chron.caption = value;
+ }
+ this.chron_levels[curr_chron_level] = chron;
+ continue;
+ }
+ if (sf in alt_chron_level_map) {
+ this.use_alt_chron = true;
+ curr_chron_level = -1;
+ curr_alt_chron_level = alt_chron_level_map[sf];
+ var chron = {};
+ if (value.match(/^\(.*\)$/)) {
+ chron.display_caption = false;
+ chron.caption = value.replace(/^\(/, '').replace(/\)$/, '');
+ } else {
+ chron.display_caption = true;
+ chron.caption = value;
+ }
+ this.alt_chron_levels[curr_alt_chron_level] = chron;
+ continue;
+ }
+
+ if (sf == 'u') {
+ var units = {
+ type : 'number'
+ };
+ if (value == 'und' || value == 'var') {
+ units.type = value;
+ } else if (!isNaN(parseInt(value))) {
+ units.value = parseInt(value);
+ } else {
+ continue; // escape garbage
+ }
+ if (curr_enum_level > 0) {
+ this.enum_levels[curr_enum_level].units_per_next_higher = units;
+ } else if (curr_alt_enum_level > 0) {
+ this.alt_enum_levels[curr_alt_enum_level].units_per_next_higher = units;
+ }
+ }
+ if (sf == 'v' && value == 'r') {
+ if (curr_enum_level > 0) {
+ this.enum_levels[curr_enum_level].restart = true;
+ } else if (curr_alt_enum_level > 0) {
+ this.alt_enum_levels[curr_alt_enum_level].restart = true;
+ }
+ }
+ if (sf == 'z') {
+ if (curr_enum_level > -1) {
+ this.enum_levels[curr_enum_level].numbering_scheme = value;
+ } else if (curr_alt_enum_level > -1) {
+ this.alt_enum_levels[curr_alt_enum_level].numbering_scheme = value;
+ }
+ }
+ if (sf == 'x') {
+ this.use_calendar_change = true;
+ value.split(',').forEach(function(chg) {
+ var calendar_change = {
+ type : null,
+ season : null,
+ month : null,
+ day : null
+ }
+ if (chg.length == 2) {
+ if (chg >= '21') {
+ calendar_change.type = 'season';
+ calendar_change.season = chg;
+ } else {
+ calendar_change.type = 'month';
+ calendar_change.month = chg;
+ }
+ } else if (chg.length == 4) {
+ calendar_change.type = 'date';
+ calendar_change.month = chg.substring(0, 2);
+ calendar_change.day = chg.substring(2, 4);
+ }
+ self.calendar_change.push(calendar_change);
+ });
+ }
+ if (sf == 'y') {
+ this.use_regularity = true;
+ var regularity_type = value.substring(0, 1);
+ var parts = [];
+ var chron_type = value.substring(1, 2);
+ value.substring(2).split(/,/).forEach(function(value) {
+ var piece = {};
+ if (regularity_type == 'c') {
+ piece.combined_code = value;
+ } else if (chron_type == 'd') {
+ if (value.match(/^\d\d$/)) {
+ piece.sub_type = 'day_of_month';
+ piece.day_of_month = value;
+ } else if (value.match(/^\d\d\d\d$/)) {
+ piece.sub_type = 'specific_date';
+ piece.specific_date = value;
+ } else {
+ piece.sub_type = 'day_of_week';
+ piece.day_of_week = value;
+ }
+ } else if (chron_type == 'm') {
+ piece.sub_type = 'month';
+ piece.month = value;
+ } else if (chron_type == 's') {
+ piece.sub_type = 'season';
+ piece.season = value;
+ } else if (chron_type == 'w') {
+ if (value.match(/^\d\d\d\d$/)) {
+ piece.sub_type = 'week_in_month';
+ piece.week = value.substring(0, 2);
+ piece.month = value.substring(2, 4);
+ } else if (value.match(/^\d\d[a-z][a-z]$/)) {
+ piece.sub_type = 'week_day';
+ piece.week = value.substring(0, 2);
+ piece.day = value.substring(2, 4);
+ } else if (value.length == 6) {
+ piece.sub_type = 'week_day_in_month';
+ piece.month = value.substring(0, 2);
+ piece.week = value.substring(2, 4);
+ piece.day = value.substring(4, 6);
+ }
+ } else if (chron_type == 'y') {
+ piece.sub_type = 'year';
+ piece.year = value;
+ }
+ parts.push(piece);
+ });
+ self.regularity.push({
+ regularity_type : regularity_type,
+ chron_type : chron_type,
+ parts : parts
+ });
+ }
+ }
+ }
+
+ if (self.frequency) {
+ if (self.frequency.match(/^\d+$/)) {
+ self.frequency_type = 'numeric';
+ self.frequency_numeric = self.frequency;
+ } else {
+ self.frequency_type = 'preset';
+ self.frequency_preset = self.frequency;
+ }
+ }
+
+ // return current pattern compiled to subfield list
+ this.compile = function() {
+ var patternCode = [];
+ patternCode.push(self.compress_expand);
+ patternCode.push(self.caption_evaluation);
+ patternCode.push('8');
+ patternCode.push(self.link);
+ if (self.use_enum) {
+ for (var i = 0; i < self.enum_levels.length; i++) {
+ patternCode.push(['a', 'b', 'c', 'd', 'e', 'f'][i]);
+ patternCode.push(self.enum_levels[i].caption);
+ if (i > 0 && self.enum_levels[i].units_per_next_higher) {
+ patternCode.push('u');
+ if (self.enum_levels[i].units_per_next_higher.type == 'number') {
+ patternCode.push(self.enum_levels[i].units_per_next_higher.value.toString());
+ } else {
+ patternCode.push(self.enum_levels[i].units_per_next_higher.type);
+ }
+ }
+ if (i > 0 && self.enum_levels[i].restart != null) {
+ patternCode.push('v');
+ patternCode.push(self.enum_levels[i].restart ? 'r' : 'c');
+ }
+ }
+ }
+ if (self.use_enum && self.use_alt_enum) {
+ for (var i = 0; i < self.alt_enum_levels.length; i++) {
+ patternCode.push(['g','h'][i]);
+ patternCode.push(self.alt_enum_levels[i].caption);
+ if (i > 0 && self.alt_enum_levels[i].units_per_next_higher) {
+ patternCode.push('u');
+ if (self.alt_enum_levels[i].units_per_next_higher.type == 'number') {
+ patternCode.push(self.alt_enum_levels[i].units_per_next_higher.value);
+ } else {
+ patternCode.push(self.alt_enum_levels[i].units_per_next_higher.type);
+ }
+ }
+ if (i > 0 && self.alt_enum_levels[i].restart != null) {
+ patternCode.push('v');
+ patternCode.push(self.alt_enum_levels[i].restart ? 'r' : 'c');
+ }
+ }
+ }
+ var chron_sfs = (self.use_enum) ? ['i', 'j', 'k', 'l'] : ['a', 'b', 'c', 'd'];
+ if (self.use_chron) {
+ for (var i = 0; i < self.chron_levels.length; i++) {
+ patternCode.push(chron_sfs[i],
+ self.chron_levels[i].display_caption ?
+ self.chron_levels[i].caption :
+ '(' + self.chron_levels[i].caption + ')'
+ );
+ }
+ }
+ var alt_chron_sf = (self.use_enum) ? 'm' : 'g';
+ if (self.use_alt_chron) {
+ patternCode.push(alt_chron_sf,
+ self.alt_chron_levels[0].display_caption ?
+ self.alt_chron_levels[0].caption :
+ '(' + self.alt_chron_levels[0].caption + ')'
+ );
+ }
+ // frequency
+ patternCode.push('w',
+ self.frequency_type == 'numeric' ?
+ self.frequency_numeric :
+ self.frequency_preset
+ );
+ // calendar change
+ if (self.use_enum && self.use_calendar_change) {
+ patternCode.push('x');
+ patternCode.push(self.calendar_change.map(function(chg) {
+ if (chg.type == 'season') {
+ return chg.season;
+ } else if (chg.type == 'month') {
+ return chg.month;
+ } else if (chg.type == 'date') {
+ return chg.month + chg.day;
+ }
+ }).join(','));
+ }
+ // regularity
+ if (self.use_regularity) {
+ self.regularity.forEach(function(reg) {
+ patternCode.push('y');
+ var val = reg.regularity_type + reg.chron_type;
+ val += reg.parts.map(function(part) {
+ if (reg.regularity_type == 'c') {
+ return part.combined_code;
+ } else if (reg.chron_type == 'd') {
+ return part[part.sub_type];
+ } else if (reg.chron_type == 'm') {
+ return part.month;
+ } else if (reg.chron_type == 'w') {
+ if (part.sub_type == 'week_in_month') {
+ return part.week + part.month;
+ } else if (part.sub_type == 'week_day') {
+ return part.week + part.day;
+ } else if (part.sub_type == 'week_day_in_month') {
+ return part.month + part.week + part.day;
+ }
+ } else if (reg.chron_type == 's') {
+ return part.season;
+ } else if (reg.chron_type == 'y') {
+ return part.year;
+ }
+ }).join(',');
+ patternCode.push(val);
+ });
+ }
+ return patternCode;
+ }
+
+ this.compile_stringify = function() {
+ return JSON.stringify(this.compile(), null, 2);
+ }
+
+ this.add_enum_level = function() {
+ if (self.enum_levels.length < 6) {
+ self.enum_levels.push({
+ caption : null,
+ units_per_next_higher : { type : 'und' },
+ restart : false
+ });
+ }
+ }
+ this.drop_enum_level = function() {
+ if (self.enum_levels.length > 1) {
+ self.enum_levels.pop();
+ }
+ }
+
+ this.add_alt_enum_level = function() {
+ if (self.alt_enum_levels.length < 2) {
+ self.alt_enum_levels.push({
+ caption : null,
+ units_per_next_higher : { type : 'und' },
+ restart : false
+ });
+ }
+ }
+ this.drop_alt_enum_level = function() {
+ if (self.alt_enum_levels.length > 1) {
+ self.alt_enum_levels.pop();
+ }
+ }
+ this.remove_calendar_change = function(idx) {
+ if (self.calendar_change.length > idx) {
+ self.calendar_change.splice(idx, 1);
+ }
+ }
+ this.add_calendar_change = function() {
+ self.calendar_change.push({
+ type : null,
+ season : null,
+ month : null,
+ day : null
+ });
+ }
+
+ this.add_chron_level = function() {
+ if (self.chron_levels.length < 4) {
+ self.chron_levels.push({
+ caption : null,
+ display_caption : false
+ });
+ }
+ }
+ this.drop_chron_level = function() {
+ if (self.chron_levels.length > 1) {
+ self.chron_levels.pop();
+ }
+ }
+ this.add_regularity = function() {
+ self.regularity.push({
+ regularity_type : null,
+ chron_type : null,
+ parts : [{ sub_type : null }]
+ });
+ }
+ this.remove_regularity = function(idx) {
+ if (self.regularity.length > idx) {
+ self.regularity.splice(idx, 1);
+ }
+ // and add a blank entry back if need be
+ if (self.regularity.length == 0) {
+ self.add_regularity();
+ }
+ }
+ this.add_regularity_part = function(reg) {
+ reg.parts.push({
+ sub_type : null
+ });
+ }
+ this.remove_regularity_part = function(reg, idx) {
+ if (reg.parts.length > idx) {
+ reg.parts.splice(idx, 1);
+ }
+ // and add a blank entry back if need be
+ if (reg.parts.length == 0) {
+ self.add_regularity_part(reg);
+ }
+ }
+
+ this.display_enum_captions = function() {
+ return self.enum_levels.map(function(lvl) {
+ return lvl.caption;
+ }).join(', ');
+ }
+ this.display_alt_enum_captions = function() {
+ return self.alt_enum_levels.map(function(lvl) {
+ return lvl.caption;
+ }).join(', ');
+ }
+ this.display_chron_captions = function() {
+ return self.chron_levels.map(function(lvl) {
+ return lvl.caption;
+ }).join(', ');
+ }
+ this.display_alt_chron_captions = function() {
+ return self.alt_chron_levels.map(function(lvl) {
+ return lvl.caption;
+ }).join(', ');
+ }
+
+ if (!patternCode) {
+ // starting from scratch, ensure there's
+ // enough so that the input wizard can be used
+ this.use_enum = true;
+ this.use_chron = true;
+ this.link = 0;
+ self.add_enum_level();
+ self.add_alt_enum_level();
+ self.add_chron_level();
+ self.add_calendar_change();
+ self.add_regularity();
+ } else {
+ // fill in potential missing bits
+ if (!self.use_enum && self.enum_levels.length == 0) self.add_enum_level();
+ if (!self.use_alt_enum && self.alt_enum_levels.length == 0) self.add_alt_enum_level();
+ if (!self.use_chron && self.chron_levels.length == 0) self.add_chron_level();
+ if (!self.use_calendar_change) self.add_calendar_change();
+ if (!self.use_regularity) self.add_regularity();
+ }
+ }
+ // TODO chron only
+
+ if ($scope.patternCode) {
+ $scope.pattern = new PredictionPattern(JSON.parse($scope.patternCode));
+ } else {
+ $scope.pattern = new PredictionPattern();
+ }
+
+ // possible sharing
+ $scope.share = {
+ pattern_name : null,
+ depth : 0
+ };
+
+ $scope.chron_captions = [];
+ $scope.alt_chron_captions = [];
+
+ $scope.handle_save = function() {
+ $scope.patternCode = JSON.stringify($scope.pattern.compile());
+ if ($scope.share.pattern_name !== null) {
+ var spt = new egCore.idl.spt();
+ spt.name($scope.share.pattern_name);
+ spt.pattern_code($scope.patternCode);
+ spt.share_depth($scope.share.depth);
+ spt.owning_lib(egCore.auth.user().ws_ou());
+ egCore.pcrud.create(spt).then(function() {
+ if (angular.isFunction($scope.onSave)) {
+ $scope.onSave($scope.patternCode);
+ }
+ });
+ } else {
+ if (angular.isFunction($scope.onSave)) {
+ $scope.onSave($scope.patternCode);
+ }
+ }
+ }
+
+}]
+ }
+})
+
+.directive('egChronSelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ ngModel : '=',
+ chronLevel : '=',
+ linkedSelector : '=',
+ },
+ templateUrl: './serials/t_chron_selector',
+ controller:
+ ['$scope','$q','egCore',
+function($scope , $q , egCore) {
+ $scope.options = [
+ { value : 'year', label : egCore.strings.CHRON_LABEL_YEAR, disabled: false },
+ { value : 'season', label : egCore.strings.CHRON_LABEL_SEASON, disabled: false },
+ { value : 'month', label : egCore.strings.CHRON_LABEL_MONTH, disabled: false },
+ { value : 'week', label : egCore.strings.CHRON_LABEL_WEEK, disabled: false },
+ { value : 'day', label : egCore.strings.CHRON_LABEL_DAY, disabled: false },
+ { value : 'hour', label : egCore.strings.CHRON_LABEL_HOUR, disabled: false }
+ ];
+ var levels = {
+ 'year' : 0,
+ 'season' : 1,
+ 'month' : 1,
+ 'week' : 2,
+ 'day' : 3,
+ 'hour' : 4
+ };
+ $scope.$watch('ngModel', function(newVal, oldVal) {
+ $scope.linkedSelector[$scope.chronLevel] = $scope.ngModel;
+ });
+ $scope.$watch('linkedSelector', function(newVal, oldVal) {
+ if ($scope.chronLevel > 0 && $scope.linkedSelector[$scope.chronLevel - 1]) {
+ var level_to_disable = levels[ $scope.linkedSelector[$scope.chronLevel - 1] ];
+ for (var i = 0; i < $scope.options.length; i++) {
+ $scope.options[i].disabled =
+ (levels[ $scope.options[i].value ] <= level_to_disable);
+ }
+ }
+ }, true);
+}]
+ }
+})
+
+.directive('egMonthSelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ ngModel : '='
+ },
+ templateUrl: './serials/t_month_selector',
+ controller:
+ ['$scope','$q',
+function($scope , $q) {
+}]
+ }
+})
+
+.directive('egSeasonSelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ ngModel : '='
+ },
+ templateUrl: './serials/t_season_selector',
+ controller:
+ ['$scope','$q',
+function($scope , $q) {
+}]
+ }
+})
+
+.directive('egWeekInMonthSelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ ngModel : '='
+ },
+ templateUrl: './serials/t_week_in_month_selector',
+ controller:
+ ['$scope','$q',
+function($scope , $q) {
+}]
+ }
+})
+
+.directive('egDayOfWeekSelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ ngModel : '='
+ },
+ templateUrl: './serials/t_day_of_week_selector',
+ controller:
+ ['$scope','$q',
+function($scope , $q) {
+}]
+ }
+})
+
+.directive('egMonthDaySelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ month : '=',
+ day : '='
+ },
+ templateUrl: './serials/t_month_day_selector',
+ controller:
+ ['$scope','$q',
+function($scope , $q) {
+ if ($scope.month == null) $scope.month = '01';
+ if ($scope.day == null) $scope.day = '01';
+ $scope.dt = new Date(2012, parseInt($scope.month) - 1, parseInt($scope.day), 1);
+ $scope.options = {
+ minMode : 'day',
+ maxMode : 'day',
+ datepickerMode : 'day',
+ showWeeks : false,
+ // use a leap year, though any publisher who uses 29 February as a
+ // calendar change is simply trolling
+ // also note that when https://github.com/angular-ui/bootstrap/issues/1993
+ // is fixed, setting minDate and maxDate would make sense, as
+ // user wouldn't be able to keeping hit the left or right arrows
+ // past the end of the range
+ // minDate : new Date('2012-01-01 00:00:01'),
+ // maxDate : new Date('2012-12-31 23:59:59'),
+ formatDayTitle : 'MMMM',
+ }
+ $scope.datePickerIsOpen = false;
+ $scope.$watch('dt', function(newVal, oldVal) {
+ if (newVal != oldVal) {
+ $scope.day = ('00' + $scope.dt.getDate() ).slice(-2);
+ $scope.month = ('00' + ($scope.dt.getMonth() + 1)).slice(-2);
+ }
+ });
+}]
+ }
+})
+
+.directive('egPredictionPatternSummary', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ pattern : '<'
+ },
+ templateUrl: './serials/t_pattern_summary',
+ controller:
+ ['$scope','$q',
+function($scope , $q) {
+}]
+ }
+})
+
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egSubSelector', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ bibId : '=',
+ ssubId : '='
+ },
+ templateUrl: './serials/t_sub_selector',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
+ '$uibModal',
+function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
+ $uibModal) {
+ if ($scope.ssubId) {
+ $scope.owning_ou = egCore.org.root();
+ }
+ $scope.owning_ou_changed = function(org) {
+ $scope.selected_owning_ou = org.id();
+ reload();
+ }
+ function reload() {
+ egSerialsCoreSvc.fetch($scope.bibId, $scope.selected_owning_ou).then(function() {
+ $scope.subscriptions = egCore.idl.toTypedHash(egSerialsCoreSvc.subTree);
+ });
+ }
+}]
+ }
+})
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egSubscriptionManager', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ bibId : '='
+ },
+ templateUrl: './serials/t_subscription_manager',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider',
+ '$uibModal','ngToast','egConfirmDialog',
+function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider ,
+ $uibModal , ngToast , egConfirmDialog ) {
+
+ $scope.selected_owning_ou = null;
+ $scope.owning_ou_changed = function(org) {
+ $scope.selected_owning_ou = org.id();
+ reload();
+ }
+
+ function reload() {
+ egSerialsCoreSvc.fetch($scope.bibId, $scope.selected_owning_ou).then(function() {
+ $scope.subscriptions = egCore.idl.toTypedHash(egSerialsCoreSvc.subTree);
+ // un-flesh receive unit template so that we can use
+ // it as a model of a select
+ angular.forEach($scope.subscriptions, function(ssub) {
+ angular.forEach(ssub.distributions, function(sdist) {
+ if (angular.isObject(sdist.receive_unit_template)) {
+ sdist.receive_unit_template = sdist.receive_unit_template.id;
+ }
+ });
+ });
+ $scope.distStreamGridDataProvider.refresh();
+ });
+ }
+ reload();
+
+ $scope.localStreamNames = [];
+ egCore.hatch.getItem('eg.serials.stream_names')
+ .then(function(list) {
+ if (list) $scope.localStreamNames = list;
+ });
+
+ $scope.distStreamGridControls = {
+ activateItem : function (item) { } // TODO
+ };
+ $scope.distStreamGridDataProvider = egGridDataProvider.instance({
+ get : function(offset, count) {
+ return this.arrayNotifier(egSerialsCoreSvc.subList, offset, count);
+ }
+ });
+
+ $scope.need_one_selected = function() {
+ var items = $scope.distStreamGridControls.selectedItems();
+ if (items.length == 1) return false;
+ return true;
+ };
+
+ $scope.receiving_templates = {};
+ angular.forEach(egCore.org.list(), function(org) {
+ egSerialsCoreSvc.fetch_templates(org.id()).then(function(list){
+ $scope.receiving_templates[org.id()] = egCore.idl.toTypedHash(list);
+ });
+ });
+
+ $scope.add_subscription = function() {
+ var new_ssub = egCore.idl.toTypedHash(new egCore.idl.ssub());
+ new_ssub._isnew = true;
+ new_ssub.record_entry = $scope.bibId;
+ new_ssub._focus_me = true;
+ $scope.subscriptions.push(new_ssub);
+ $scope.add_distribution(new_ssub); // since we know we want at least one distribution
+ }
+ $scope.add_distribution = function(ssub, grab_focus) {
+ egCore.org.settings([
+ 'serial.default_display_grouping'
+ ]).then(function(set) {
+ var new_sdist = egCore.idl.toTypedHash(new egCore.idl.sdist());
+ new_sdist._isnew = true;
+ new_sdist.subscription = ssub.id;
+ new_sdist.display_grouping = set['serial.default_display_grouping'] || 'chron';
+ if (!angular.isArray(ssub.distributions)){
+ ssub.distributions = [];
+ }
+ if (grab_focus) {
+ new_sdist._focus_me = true;
+ ssub._focus_me = false;
+ }
+ ssub.distributions.push(new_sdist);
+ $scope.add_stream(new_sdist); // since we know we want at least one stream
+ });
+ }
+ $scope.remove_pending_distribution = function(ssub, sdist) {
+ var to_remove = -1;
+ for (var i = 0; i < ssub.distributions.length; i++) {
+ if (ssub.distributions[i] === sdist) {
+ to_remove = i;
+ break;
+ }
+ }
+ if (to_remove > -1) {
+ ssub.distributions.splice(to_remove, 1);
+ }
+ }
+ $scope.add_stream = function(sdist, grab_focus) {
+ var new_sstr = egCore.idl.toTypedHash(new egCore.idl.sstr());
+ new_sstr.distribution = sdist.id;
+ new_sstr._isnew = true;
+ if (grab_focus) {
+ new_sstr._focus_me = true;
+ sdist._has_focus = false; // and take focus away from a newly created sdist
+ }
+ if (!angular.isArray(sdist.streams)){
+ sdist.streams = [];
+ }
+ sdist.streams.push(new_sstr);
+ $scope.dirtyForm();
+ }
+ $scope.remove_pending_stream = function(sdist, sstr) {
+ var to_remove = -1;
+ for (var i = 0; i < sdist.streams.length; i++) {
+ if (sdist.streams[i] === sstr) {
+ to_remove = i;
+ break;
+ }
+ }
+ if (to_remove > -1) {
+ sdist.streams.splice(to_remove, 1);
+ }
+ }
+
+ $scope.abort_changes = function(form) {
+ reload();
+ form.$setPristine();
+ }
+ function updateLocalStreamNames (new_name) {
+ if (new_name && $scope.localStreamNames.filter(function(x){ return x == new_name}).length == 0) {
+ $scope.localStreamNames.push(new_name);
+ egCore.hatch.setItem('eg.serials.stream_names', $scope.localStreamNames)
+ }
+ }
+
+ $scope.dirtyForm = function () {
+ $scope.ssubform.$dirty = true;
+ }
+
+ $scope.save_subscriptions = function(form) {
+ // traverse through structure and set _ischanged
+ // TODO add more granular dirty input detection
+ angular.forEach($scope.subscriptions, function(ssub) {
+ if (!ssub._isnew) ssub._ischanged = true;
+ angular.forEach(ssub.distributions, function(sdist) {
+ if (!sdist._isnew) sdist._ischanged = true;
+ angular.forEach(sdist.streams, function(sstr) {
+ if (!sstr._isnew) sstr._ischanged = true;
+ updateLocalStreamNames(sstr.routing_label);
+ });
+ });
+ });
+
+ var obj = egCore.idl.fromTypedHash($scope.subscriptions);
+
+ // create a bunch of promises that each get resolved upon each
+ // CUD update; that way, we can know when the entire save
+ // operation is completed
+ var promises = [];
+ angular.forEach(obj, function(ssub) {
+ ssub._cud_done = $q.defer();
+ promises.push(ssub._cud_done.promise);
+ angular.forEach(ssub.distributions(), function(sdist) {
+ sdist._cud_done = $q.defer();
+ promises.push(sdist._cud_done.promise);
+ angular.forEach(sdist.streams(), function(sstr) {
+ sstr._cud_done = $q.defer();
+ promises.push(sstr._cud_done.promise);
+ });
+ });
+ });
+
+ angular.forEach(obj, function(ssub) {
+ ssub.owning_lib(ssub.owning_lib().id()); // deflesh
+ egCore.pcrud.apply(ssub).then(function(res) {
+ var ssub_id = (ssub.isnew() && angular.isObject(res)) ? res.id() : ssub.id();
+ angular.forEach(ssub.distributions(), function(sdist) {
+ // set subscription ID just in case it's new
+ sdist.holding_lib(sdist.holding_lib().id()); // deflesh
+ sdist.subscription(ssub_id);
+ egCore.pcrud.apply(sdist).then(function(res) {
+ var sdist_id = (sdist.isnew() && angular.isObject(res)) ? res.id() : sdist.id();
+ angular.forEach(sdist.streams(), function(sstr) {
+ // set distribution ID just in case it's new
+ sstr.distribution(sdist_id);
+ egCore.pcrud.apply(sstr).then(function(res) {
+ sstr._cud_done.resolve();
+ });
+ });
+ });
+ sdist._cud_done.resolve();
+ });
+ ssub._cud_done.resolve();
+ });
+ });
+ $q.all(promises).then(function(resolutions) {
+ reload();
+ form.$setPristine();
+ });
+ }
+ $scope.delete_subscription = function(rows) {
+ if (rows.length == 0) { return; }
+ var s_rows = rows.filter(function(el) {
+ return typeof el['id'] != 'undefined';
+ });
+ if (s_rows.length == 0) { return; }
+ egConfirmDialog.open(
+ egCore.strings.CONFIRM_DELETE_SUBSCRIPTION,
+ egCore.strings.CONFIRM_DELETE_SUBSCRIPTION_MESSAGE,
+ {count : s_rows.length}
+ ).result.then(function () {
+ var promises = [];
+ angular.forEach(s_rows, function(el) {
+ promises.push(
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.subscription.safe_delete',
+ egCore.auth.token(),
+ el['id']
+ ).then(function(resp){
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_DELETE + ' : ' + evt.desc);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_SUBSCRIPTION_SUCCESS_DELETE);
+ }
+ })
+ );
+ });
+ $q.all(promises).then(function() {
+ reload();
+ });
+ });
+ }
+ $scope.delete_distribution = function(rows) {
+ if (rows.length == 0) { return; }
+ var d_rows = rows.filter(function(el) {
+ return typeof el['sdist.id'] != 'undefined';
+ });
+ if (d_rows.length == 0) { return; }
+ egConfirmDialog.open(
+ egCore.strings.CONFIRM_DELETE_DISTRIBUTION,
+ egCore.strings.CONFIRM_DELETE_DISTRIBUTION_MESSAGE,
+ {count : d_rows.length}
+ ).result.then(function () {
+ var promises = [];
+ angular.forEach(d_rows, function(el) {
+ promises.push(
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.distribution.safe_delete',
+ egCore.auth.token(),
+ el['sdist.id']
+ ).then(function(resp){
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_DELETE + ' : ' + evt.desc);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_DELETE);
+ }
+ })
+ );
+ });
+ $q.all(promises).then(function() {
+ reload();
+ });
+ });
+ }
+ $scope.delete_stream = function(rows) {
+ if (rows.length == 0) { return; }
+ var s_rows = rows.filter(function(el) {
+ return typeof el['sstr.id'] != 'undefined';
+ });
+ if (s_rows.length == 0) { return; }
+ egConfirmDialog.open(
+ egCore.strings.CONFIRM_DELETE_STREAM,
+ egCore.strings.CONFIRM_DELETE_STREAM_MESSAGE,
+ {count : s_rows.length}
+ ).result.then(function () {
+ var promises = [];
+ angular.forEach(s_rows, function(el) {
+ promises.push(
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.stream.safe_delete',
+ egCore.auth.token(),
+ el['sstr.id']
+ ).then(function(resp){
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_STREAM_FAIL_DELETE + ' : ' + evt.desc);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_STREAM_SUCCESS_DELETE);
+ }
+ })
+ );
+ });
+ $q.all(promises).then(function() {
+ reload();
+ });
+ });
+ }
+ $scope.additional_routing = function(rows) {
+ if (!rows) { return; }
+ var row = rows[0];
+ if (!row) { row = $scope.distStreamGridControls.selectedItems()[0]; }
+ if (row && row['sstr.id']) {
+ egCore.pcrud.search('srlu', {
+ stream : row['sstr.id']
+ }, {
+ flesh : 2,
+ flesh_fields : {
+ 'srlu' : ['reader'],
+ 'au' : ['mailing_address','billing_address','home_ou']
+ },
+ order_by : { srlu : 'pos' }
+ },
+ { atomic : true }
+ ).then(function(list) {
+ $uibModal.open({
+ templateUrl: './serials/t_routing_list',
+ controller: 'RoutingCtrl',
+ resolve : {
+ rowInfo : function() {
+ return row;
+ },
+ routes : function() {
+ return egCore.idl.toHash(list);
+ }
+ }
+ }).result.then(function(routes) {
+ // delete all of the routes first;
+ // it's easiest given the constraints
+ var deletions = [];
+ var creations = [];
+ angular.forEach(routes, function(r) {
+ var srlu = new egCore.idl.srlu();
+ srlu.stream(r.stream);
+ srlu.pos(r.pos);
+ if (r.reader) {
+ srlu.reader(r.reader.id);
+ }
+ srlu.department(r.department);
+ srlu.note(r.note);
+ if (r.id) {
+ srlu.id(r.id);
+ var srlu_copy = angular.copy(srlu);
+ srlu_copy.isdeleted(true);
+ deletions.push(srlu_copy);
+ }
+ if (!r.delete_me) {
+ srlu.isnew(true);
+ creations.push(srlu);
+ }
+ });
+ egCore.pcrud.apply(deletions.concat(creations)).then(function(){
+ reload();
+ });
+ });
+ });
+ }
+ }
+ $scope.clone_subscription = function(rows) {
+ if (!rows) { return; }
+ var row = rows[0];
+ $uibModal.open({
+ templateUrl: './serials/t_clone_subscription',
+ controller: 'CloneCtrl',
+ resolve : {
+ subs : function() {
+ return rows;
+ }
+ },
+ windowClass: 'app-modal-window',
+ backdrop: 'static',
+ keyboard: false
+ }).result.then(function(args) {
+ var promises = [];
+ var some_failure = false;
+ var some_success = false;
+ var seen = {};
+ angular.forEach(rows, function(row) {
+ //console.log(row);
+ if (!seen[row.id]) {
+ seen[row.id] = 1;
+ promises.push(
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.subscription.clone',
+ egCore.auth.token(),
+ row.id,
+ args.bib_id
+ ).then(
+ function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) { // any way to just throw or return this to the error handler?
+ console.log('failure',resp);
+ some_failure = true;
+ ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_CLONE);
+ } else {
+ console.log('success',resp);
+ some_success = true;
+ ngToast.success(egCore.strings.SERIALS_SUBSCRIPTION_SUCCESS_CLONE);
+ }
+ },
+ function(resp) {
+ console.log('failure',resp);
+ some_failure = true;
+ ngToast.danger(egCore.strings.SERIALS_SUBSCRIPTION_FAIL_CLONE);
+ }
+ )
+ );
+ }
+ });
+ $q.all(promises).then(function() {
+ reload();
+ });
+ });
+ }
+ $scope.link_mfhd = function(rows) {
+ if (!rows) { return; }
+ var row = rows[0];
+ if (!row['sdist.id']) { return; }
+ $uibModal.open({
+ templateUrl: './serials/t_link_mfhd',
+ controller: 'LinkMFHDCtrl',
+ resolve : {
+ row : function() {
+ return rows[0];
+ },
+ bibId : function() {
+ return $scope.bibId;
+ }
+ },
+ windowClass: 'app-modal-window',
+ backdrop: 'static',
+ keyboard: false
+ }).result.then(function(args) {
+ console.log('modal done', args);
+ egCore.pcrud.search('sdist', {
+ id: rows[0]['sdist.id']
+ }, {}, { atomic : true }
+ ).then(function(resp){
+ var evt = egCore.evt.parse(resp);
+ if (evt) { // any way to just throw or return this to the error handler?
+ console.log('failure',resp);
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
+ }
+ var sdist = resp[0];
+ sdist.ischanged(true);
+ sdist.summary_method( args.summary_method );
+ sdist.record_entry( args.which_mfhd );
+ egCore.pcrud.apply(sdist).then(
+ function(resp) { // maybe success
+ console.log('apply',resp);
+ var evt = egCore.evt.parse(resp);
+ if (evt) { // any way to just throw or return this to the error handler?
+ console.log('failure',resp);
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
+ } else {
+ console.log('success',resp);
+ ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_LINK_MFHD);
+ reload();
+ }
+ },
+ function(resp) {
+ console.log('failure',resp);
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_LINK_MFHD);
+ }
+ );
+ });
+ });
+ }
+ $scope.apply_binding_template = function(rows) {
+ if (rows.length == 0) { return; }
+ var d_rows = rows.filter(function(el) {
+ return typeof el['sdist.id'] != 'undefined';
+ });
+ if (d_rows.length == 0) { return; }
+ var libs = []; var seen_lib = {};
+ angular.forEach(d_rows, function(el) {
+ if (el['sdist.holding_lib.id'] && !seen_lib[el['sdist.holding_lib.id']]) {
+ seen_lib[el['sdist.holding_lib.id']] = 1;
+ libs.push({
+ id: el['sdist.holding_lib.id'],
+ name: el['sdist.holding_lib.name'],
+ });
+ }
+ });
+ $uibModal.open({
+ templateUrl: './serials/t_apply_binding_template',
+ controller: 'ApplyBindingTemplateCtrl',
+ resolve : {
+ rows : function() {
+ return d_rows;
+ },
+ libs : function() {
+ return libs;
+ }
+ },
+ windowClass: 'app-modal-window',
+ backdrop: 'static',
+ keyboard: false
+ }).result.then(function(args) {
+ console.log(args);
+ egCore.pcrud.search('sdist', {
+ id: d_rows.map(function(el) { return el['sdist.id']; })
+ }, {}, { atomic : true }
+ ).then(function(resp){
+ var evt = egCore.evt.parse(resp);
+ if (evt) { // any way to just throw or return this to the error handler?
+ console.log('failure',resp);
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
+ }
+ var promises = [];
+ angular.forEach(resp,function(sdist) {
+ var promise = $q.defer();
+ promises.push(promise.promise);
+ sdist.ischanged(true);
+ sdist.bind_unit_template(
+ typeof args.bind_unit_template[sdist.holding_lib()] == 'undefined'
+ ? null
+ : args.bind_unit_template[sdist.holding_lib()]
+ );
+ egCore.pcrud.apply(sdist).then(
+ function(resp2) { // maybe success
+ console.log('apply',resp2);
+ var evt = egCore.evt.parse(resp2);
+ if (evt) { // any way to just throw or return this to the error handler?
+ console.log('failure',resp2);
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
+ } else {
+ console.log('success',resp2);
+ ngToast.success(egCore.strings.SERIALS_DISTRIBUTION_SUCCESS_BINDING_TEMPLATE);
+ }
+ promise.resolve();
+ },
+ function(resp2) {
+ console.log('failure',resp2);
+ ngToast.danger(egCore.strings.SERIALS_DISTRIBUTION_FAIL_BINDING_TEMPLATE);
+ promise.resolve();
+ }
+ );
+ });
+ $q.all(promises).then(function() {
+ reload();
+ });
+ });
+ });
+ }
+ $scope.subscription_notes = function(rows) {
+ return $scope.notes('subscription',rows);
+ }
+ $scope.distribution_notes = function(rows) {
+ return $scope.notes('distribution',rows);
+ }
+ $scope.notes = function(note_type,rows) {
+ if (!rows) { return; }
+
+ function modal(existing_notes) {
+ $uibModal.open({
+ templateUrl: './serials/t_notes',
+ animation: true,
+ controller: 'NotesCtrl',
+ resolve : {
+ note_type : function() { return note_type; },
+ rows : function() {
+ return rows;
+ },
+ notes : function() {
+ return existing_notes;
+ }
+ },
+ windowClass: 'app-modal-window',
+ backdrop: 'static',
+ keyboard: false
+ }).result.then(function(notes) {
+ console.log('results',notes);
+ egCore.pcrud.apply(notes).then(
+ function(a) { console.log('toast here 1',a); },
+ function(a) { console.log('toast here 2',a); }
+ );
+ });
+ }
+
+ if (rows.length == 1) {
+ var fm_hint;
+ var search_hash = {};
+ var search_opt = {};
+ switch(note_type) {
+ case 'subscription':
+ fm_hint = 'ssubn';
+ search_hash.subscription = rows[0]['id'];
+ search_opt.order_by = { ssubn : 'create_date' };
+ break;
+ case 'distribution':
+ fm_hint = 'sdistn';
+ search_hash.distribution = rows[0]['sdist.id'];
+ search_opt.order_by = { sdistn : 'create_date' };
+ break;
+ case 'item': default:
+ fm_hint = 'sin';
+ search_hash.item = rows[0]['si.id'];
+ search_opt.order_by = { sin : 'create_date' };
+ break;
+ }
+ egCore.pcrud.search(fm_hint, search_hash, search_opt,
+ { atomic : true }
+ ).then(function(list) {
+ modal(list);
+ });
+ } else {
+ // support batch creation of notes across selections,
+ // but not editing
+ modal([]);
+ }
+ }
+
+}]
+ }
+})
+
+.controller('ApplyBindingTemplateCtrl',
+ ['$scope','$q','$uibModalInstance','egCore','egSerialsCoreSvc',
+ 'rows','libs',
+function($scope , $q , $uibModalInstance , egCore , egSerialsCoreSvc ,
+ rows , libs ) {
+ $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ $scope.libs = libs;
+ $scope.rows = rows;
+ $scope.args = { bind_unit_template : {} };
+ $scope.templates = {};
+ angular.forEach(libs, function(org) {
+ egSerialsCoreSvc.fetch_templates(org.id).then(function(list){
+ $scope.templates[org.id] = egCore.idl.toTypedHash(list);
+ });
+ });
+}])
+
+.controller('LinkMFHDCtrl',
+ ['$scope','$q','$uibModalInstance','egCore','row','bibId',
+function($scope , $q , $uibModalInstance , egCore , row , bibId ) {
+ console.log('row',row);
+ console.log('bibId',bibId);
+ $scope.args = {
+ summary_method: row['sdist.summary_method'] || 'add_to_sre',
+ };
+ if (row['sdist.record_entry']) {
+ $scope.args.which_mfhd = row['sdist.record_entry'].id;
+ }
+ $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ $scope.legacies = {};
+ egCore.pcrud.search('sre', {
+ record: bibId, owning_lib : row['sdist.holding_lib.id'], active: 't', deleted: 'f'
+ }, {}, { atomic : true }
+ ).then(
+ function(resp) { // maybe success
+ var evt; if (evt = egCore.evt.parse(resp)) { console.error(evt.toString()); return; }
+ if (!resp) { return; }
+
+ var promises = [];
+ var seen = {};
+
+ angular.forEach(resp, function(sre) {
+ console.log('sre',sre);
+ if (!seen[sre.record()]) {
+ seen[sre.record()] = 1;
+ $scope.legacies[sre.record()] = { mvr: null, svrs: [] };
+ promises.push(
+ egCore.net.request(
+ 'open-ils.search',
+ 'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
+ sre.record()
+ ).then(function(resp2) {
+ var evt; if (evt = egCore.evt.parse(resp2)) { console.error(evt.toString()); return; }
+ if (!resp2) { return; }
+ $scope.legacies[sre.record()].mvr = egCore.idl.toHash(resp2);
+ })
+ );
+ promises.push(
+ egCore.net.request(
+ 'open-ils.search',
+ 'open-ils.search.serial.record.bib.retrieve',
+ sre.record(),
+ row['owning_lib.id']
+ ).then(function(resp2) {
+ angular.forEach(resp2,function(r) {
+ if (r.sre_id() > 0) {
+ console.log('svr',egCore.idl.toHash(r));
+ $scope.legacies[sre.record()].svrs.push( egCore.idl.toHash(r) );
+ }
+ });
+ })
+ );
+ }
+ if (typeof $scope.legacies[sre.record()].sres == 'undefined') {
+ $scope.legacies[sre.record()].sres = {};
+ }
+ $scope.legacies[sre.record()].sres[sre.id()] = egCore.idl.toHash(sre);
+ });
+
+ $q.all(promises).then(function(){
+ console.log('done',$scope.legacies);
+ });
+ },
+ function(resp) { // outright failure
+ console.error('failure',resp);
+ }
+ )
+}])
+
+.controller('CloneCtrl',
+ ['$scope','$uibModalInstance','egCore','subs',
+function($scope , $uibModalInstance , egCore , subs ) {
+ $scope.args = {};
+ $scope.ok = function(count) { $uibModalInstance.close($scope.args) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ $scope.subs = subs;
+ $scope.find_bib = function () {
+
+ $scope.bibNotFound = null;
+ $scope.mvr = null;
+ if (!$scope.args.bib_id) return;
+
+ return egCore.net.request(
+ 'open-ils.search',
+ 'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
+ $scope.args.bib_id
+ ).then(
+ function(resp) { // maybe success
+
+ if (evt = egCore.evt.parse(resp)) {
+ $scope.bibNotFound = $scope.args.bib_id;
+ console.error(evt.toString());
+ return;
+ }
+
+ if (!resp) {
+ $scope.bibNotFound = $scope.args.bib_id;
+ return;
+ }
+
+ $scope.mvr = egCore.idl.toHash(resp);
+ //console.log($scope.mvr);
+ },
+ function(resp) { // outright failure
+ console.error(resp);
+ $scope.bibNotFound = $scope.args.bib_id;
+ return;
+ }
+ );
+ }
+ $scope.$watch("args.bib_id", function(newVal, oldVal) {
+ if (newVal && newVal != oldVal) {
+ $scope.find_bib();
+ }
+ });
+}])
+
+.controller('RoutingCtrl',
+ ['$scope','$uibModalInstance','egCore','rowInfo','routes',
+function($scope , $uibModalInstance , egCore , rowInfo , routes ) {
+ $scope.args = {
+ which_radio_button: 'reader'
+ ,reader: ''
+ ,department: ''
+ ,delete_me: false
+ };
+ $scope.stream_id = rowInfo['sstr.id'];
+ $scope.stream_label = rowInfo['sstr.routing_label'];
+ $scope.routes = routes;
+ $scope.readerInFocus = true;
+ $scope.ok = function(count) { $uibModalInstance.close($scope.routes) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ $scope.model_has_changed = false;
+ $scope.find_user = function () {
+
+ $scope.readerNotFound = null;
+ $scope.reader_obj = null;
+ if (!$scope.args.reader) return;
+
+ egCore.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.get_barcodes',
+ egCore.auth.token(), egCore.auth.user().ws_ou(),
+ 'actor', $scope.args.reader)
+
+ .then(function(resp) { // get_barcodes
+
+ if (evt = egCore.evt.parse(resp)) {
+ console.error(evt.toString());
+ return;
+ }
+
+ if (!resp || !resp[0]) {
+ $scope.readerNotFound = $scope.args.reader;
+ return;
+ }
+
+ egCore.pcrud.search('au', {
+ id : resp[0].id
+ }, {
+ flesh : 1,
+ flesh_fields : {
+ 'au' : ['mailing_address','billing_address','home_ou']
+ }
+ },
+ { atomic : true }
+ ).then(function(usr) {
+ $scope.reader_obj = egCore.idl.toHash(usr[0]);
+ });
+ });
+ }
+ $scope.add_route = function () {
+ var new_route = {
+ stream: $scope.stream_id
+ ,pos: $scope.routes.length
+ ,note: $scope.args.note
+ }
+ if ($scope.args.which_radio_button == 'reader') {
+ new_route.reader = $scope.reader_obj;
+ } else {
+ new_route.department = $scope.args.department;
+ }
+ $scope.routes.push(new_route);
+ $scope.model_has_changed = true;
+ }
+ function adjust_pos_field() {
+ var idx = 0;
+ for (var i = 0; i < $scope.routes.length; i++) {
+ $scope.routes[i].pos = $scope.routes[i].delete_me ? idx : idx++;
+ }
+ $scope.model_has_changed = true;
+ }
+ $scope.move_route_up = function(r) {
+ var pos = r.pos;
+ if (pos > 0) {
+ var temp = $scope.routes[ pos - 1 ];
+ $scope.routes[ pos - 1 ] = $scope.routes[ pos ];
+ $scope.routes[ pos ] = temp;
+ adjust_pos_field();
+ }
+ }
+ $scope.move_route_down = function(r) {
+ var pos = r.pos;
+ if (pos < $scope.routes.length - 1) {
+ var temp = $scope.routes[ pos + 1 ];
+ $scope.routes[ pos + 1 ] = $scope.routes[ pos ];
+ $scope.routes[ pos ] = temp;
+ adjust_pos_field();
+ }
+ }
+ $scope.toggle_delete = function(r) {
+ r.delete_me = ! r.delete_me;
+ adjust_pos_field();
+ }
+ $scope.$watch("args.reader", function(newVal, oldVal) {
+ if (newVal && newVal != oldVal) {
+ $scope.find_user();
+ }
+ });
+}])
+
+.controller('NotesCtrl',
+ ['$scope','$uibModalInstance','egCore','note_type','rows','notes',
+function($scope , $uibModalInstance , egCore , note_type , rows , notes ) {
+ $scope.note_type = note_type;
+ $scope.focusNote = true;
+ $scope.note = {
+ creator : egCore.auth.user().id(),
+ title : '',
+ value : '',
+ pub : false,
+ 'alert' : false,
+ };
+
+ $scope.require_initials = false;
+ egCore.org.settings([
+ 'ui.staff.require_initials.copy_notes'
+ ]).then(function(set) {
+ $scope.require_initials = Boolean(set['ui.staff.require_initials.copy_notes']);
+ });
+
+ $scope.note_list = notes;
+
+ $scope.ok = function(note) {
+
+ var return_notes = [];
+ if (note.initials) note.value += ' [' + note.initials + ']';
+ if ( (typeof note.title != 'undefined' && note.title != '')
+ || (typeof note.value != 'undefined' && note.value != '')) {
+ angular.forEach(rows, function (r) {
+ console.log('r',r);
+ window.my_r = r;
+ var n;
+ switch(note_type) {
+ case 'subscription':
+ n = new egCore.idl.ssubn();
+ n.subscription(r['id']);
+ break;
+ case 'distribution':
+ n = new egCore.idl.sdistn();
+ n.distribution(r['sdist.id']);
+ break;
+ case 'item':
+ default:
+ n = new egCore.idl.sin();
+ n.item(r['si.id']);
+ }
+ n.isnew(true);
+ n.creator(note.creator);
+ n.pub(note.pub);
+ n['alert'](note['alert']);
+ n.title(note.title);
+ n.value(note.value);
+ return_notes.push( n );
+ });
+ }
+ angular.forEach(notes, function(n) {
+ if (n.ischanged() || n.isdeleted()) {
+ return_notes.push( n );
+ }
+ });
+ window.return_notes = return_notes;
+ $uibModalInstance.close(return_notes);
+ }
+
+ $scope.cancel = function($event) {
+ $uibModalInstance.dismiss();
+ $event.preventDefault();
+ }
+}])
--- /dev/null
+angular.module('egSerialsAppDep')
+
+.directive('egItemGrid', function() {
+ return {
+ transclude: true,
+ restrict: 'E',
+ scope: {
+ bibId : '=',
+ ssubId : '='
+ },
+ templateUrl: './serials/t_view_items_grid',
+ controller:
+ ['$scope','$q','egSerialsCoreSvc','egCore','egGridDataProvider','orderByFilter',
+ '$uibModal','ngToast','egConfirmDialog','egPromptDialog','$timeout',
+function($scope , $q , egSerialsCoreSvc , egCore , egGridDataProvider , orderByFilter ,
+ $uibModal , ngToast , egConfirmDialog , egPromptDialog , $timeout) {
+
+ $scope.svc = egSerialsCoreSvc;
+
+ var _paging_filter;
+ function reload(ssubId,filter) {
+ _paging_filter = filter;
+ return egSerialsCoreSvc.fetchItemsForSub(ssubId,filter).then(function() {
+ $scope.itemGridProvider.refresh();
+ });
+ }
+
+ $scope.filter_items_all = function () { return reload($scope.ssubId) }
+ $scope.filter_items_have = function () { return reload($scope.ssubId,{status:['Received','Bindery','Bound']}) }
+ $scope.filter_items_dont_have = function () { return reload($scope.ssubId,{'-not':{status:['Received','Bindery','Bound']}}) }
+ $scope.filter_items_by_status = function (item,status) { return reload($scope.ssubId,{status:status.name}) }
+
+ $scope.$watch('ssubId', function(newVal, oldVal) {
+ if (newVal && newVal != oldVal) return reload(newVal);
+ });
+
+ $scope.itemGridControls = {
+ activateItem : function (item) { } // TODO
+ };
+
+ function compileSort(sort) {
+ if (sort && angular.isArray(sort) && sort.length == 1) {
+ if (angular.isObject(sort[0])) {
+ for (key in sort[0]) {
+ return {
+ 'class' : 'sitem',
+ field : key,
+ direction : sort[0][key]
+ };
+ }
+ } else {
+ return { 'class': 'sitem', field: sort[0] };
+ }
+ }
+ }
+ var current_sort = [];
+ $scope.itemGridProvider = egGridDataProvider.instance({
+ get : function(offset, count) {
+ var self = this;
+ if (angular.equals(current_sort, self.sort) && egSerialsCoreSvc.itemList.length >= offset + count) { // if there's anything on the requested page, notify
+ return self.arrayNotifier(egSerialsCoreSvc.itemList, offset, count);
+ } else { // else try to fetch another page
+ if (angular.equals(current_sort, self.sort)) {
+ return egSerialsCoreSvc.fetchItemsForSubPaged(
+ $scope.ssubId,
+ _paging_filter,
+ egSerialsCoreSvc.itemList.length,
+ count + offset - egSerialsCoreSvc.itemList.length,
+ compileSort(self.sort)
+ ).then(function() {
+ return self.arrayNotifier(egSerialsCoreSvc.itemList, offset, count);
+ });
+ } else {
+ current_sort = self.sort;
+ return egSerialsCoreSvc.fetchItemsForSub(
+ $scope.ssubId,
+ _paging_filter,
+ null,
+ compileSort(self.sort)
+ ).then(function() {
+ return self.arrayNotifier(egSerialsCoreSvc.itemList, offset, count);
+ });
+ }
+ }
+ }
+ });
+
+ $scope.delete_items = function (items) {
+ var list = [];
+
+ angular.forEach(items, function (i) {
+ var obj = egCore.idl.fromHash('sitem',i);
+ obj.isdeleted(1);
+ obj.stream(obj.stream().id); // API wants scalar or FM object
+ obj.issuance(obj.issuance().id);
+ list.push(obj);
+ });
+
+ egConfirmDialog.open(
+ egCore.strings.CONFIRM_CHANGE_ITEMS.delete,
+ egCore.strings.CONFIRM_CHANGE_ITEMS_MESSAGE.delete,
+ {items : list.length}
+ ).result.then(function () {
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.item.fleshed.batch.update',
+ egCore.auth.token(),
+ list
+ ).then( function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ return reload($scope.ssubId,_paging_filter);
+ }
+ });
+ });
+ }
+
+ $scope.edit_issuance_holding_code = function (items) {
+ var promises = [];
+ var edits = [];
+ angular.forEach(items.reverse(), function (item) {
+ promises.push( egSerialsCoreSvc.new_holding_code({
+ title : egCore.strings.SERIALS_EDIT_SISS_HC,
+ curr_iss : egCore.idl.fromHash('siss',item.issuance),
+ label : item.issuance.label,
+ type : item.issuance.type ? item.issuance.type : 'basic',
+ can_change_adhoc : true
+ }).then(function(result) {
+ if (!result.adhoc) {
+ item.issuance.holding_code = JSON.stringify(result.holding_code);
+ item.issuance.holding_type = result.type;
+ } else {
+ item.issuance.label = result.label;
+ item.issuance.holding_type = result.type;
+ }
+
+ item.issuance.date_published = result.date.toISOString();
+ item.issuance.editor = egCore.auth.user();
+ item.issuance.edit_date = 'now';
+
+ var iss = egCore.idl.fromHash('siss',item.issuance);
+ if (!result.adhoc) { // not an ad hoc issuance, get predicted label
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.make_prediction_values',
+ egCore.auth.token(),
+ { ssub_id : $scope.ssubId,
+ num_to_predict : 0,
+ include_base_issuance : 1,
+ base_issuance : iss
+ }
+ ).then( function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ } else {
+ iss.label(resp[0].label);
+ edits.push(iss);
+ }
+ });
+ }
+
+ return $q.when(edits.push(iss));
+ })
+ );
+ });
+ return $q.all(promises)
+ .finally(function() {
+ if (edits.length) return update_issuances(edits);
+ });
+ }
+
+
+ function update_issuances (list) {
+ if (!angular.isArray(list)) list = [list];
+
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.issuance.fleshed.batch.update',
+ egCore.auth.token(),
+ list
+ ).then(
+ function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ return reload($scope.ssubId,_paging_filter);
+ }
+ },
+ function(resp) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ }
+ );
+ }
+
+
+ $scope.following_issuance = function (items) {
+ return egSerialsCoreSvc.new_holding_code({
+ title : egCore.strings.SERIALS_ISSUANCE_ADD,
+ prev_iss : egCore.idl.fromHash('siss',items[0].issuance),
+ can_change_adhoc : true
+ }).then(function(hc) {
+ if (hc.adhoc) {
+ var new_iss = new egCore.idl.siss();
+ new_iss.creator( egCore.auth.user().id() );
+ new_iss.editor( egCore.auth.user().id() );
+ new_iss.date_published( hc.date.toISOString() );
+ new_iss.subscription( $scope.ssubId );
+ new_iss.label( hc.label );
+ new_iss.holding_type( hc.type );
+
+ return egCore.pcrud.create(new_iss).then(function(issuance) {
+ var new_item = new egCore.idl.sitem();
+ new_item.creator( egCore.auth.user().id() );
+ new_item.editor( egCore.auth.user().id() );
+ new_item.issuance( issuance.id() );
+ new_item.stream( items[0].stream.id );
+ new_item.date_expected( hc.date.toISOString() ); // XXX do we have interval math?
+
+ return egCore.pcrud.create(new_item).then(function() {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ return reload($scope.ssubId,_paging_filter);
+ },function (error) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ });
+ },function (error) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ });
+ }
+
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.make_predictions',
+ egCore.auth.token(),
+ { ssub_id : $scope.ssubId,
+ num_to_predict : 1,
+ base_issuance : egCore.idl.fromHash('siss',items[0].issuance)
+ }
+ ).then(
+ function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ return reload($scope.ssubId,_paging_filter);
+ }
+ },
+ function(resp) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ }
+ );
+ });
+ }
+
+ $scope.add_special_issuance = function() {
+ return egSerialsCoreSvc.new_holding_code({
+ title : egCore.strings.SERIALS_SPECIAL_ISSUANCE_ADD,
+ can_change_adhoc : false,
+ adhoc : true
+ }).then(function(hc) {
+ // perforce add hoc
+ var new_iss = new egCore.idl.siss();
+ new_iss.creator( egCore.auth.user().id() );
+ new_iss.editor( egCore.auth.user().id() );
+ new_iss.date_published( hc.date.toISOString() );
+ new_iss.subscription( $scope.ssubId );
+ new_iss.label( hc.label );
+ new_iss.holding_type( hc.type );
+
+ return egCore.pcrud.create(new_iss).then(function(issuance) {
+ var new_items = [];
+ var sub = egSerialsCoreSvc.get_ssub($scope.ssubId);
+ angular.forEach(sub.distributions(), function(dist) {
+ angular.forEach(dist.streams(), function(stream) {
+ var new_item = new egCore.idl.sitem();
+ new_item.creator( egCore.auth.user().id() );
+ new_item.editor( egCore.auth.user().id() );
+ new_item.issuance( issuance.id() );
+ new_item.stream( stream.id() );
+ new_item.date_expected( hc.date.toISOString() ); // XXX do we have interval math?
+ new_items.push(new_item);
+ });
+ });
+ var promises = [];
+ angular.forEach(new_items, function(item) {
+ promises.push(egCore.pcrud.create(item));
+ });
+
+ $q.all(promises).then(function() {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ return reload($scope.ssubId,_paging_filter);
+ },function (error) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ });
+ });
+ });
+ }
+
+ $scope.do_print_routing_lists = false;
+ egCore.hatch.getItem('eg.serials.items.do_print_routing_lists').then(function(val) {
+ $scope.do_print_routing_lists = val;
+ });
+
+ $scope.receive_and_barcode = false;
+ egCore.hatch.getItem('eg.serials.items.receive_and_barcode').then(function(val) {
+ $scope.receive_and_barcode = val;
+ });
+
+ $scope.checkbox_handler = function(item) {
+ $scope[item.checkbox] = item.checked;
+ egCore.hatch.setItem('eg.serials.items.'+item.checkbox, item.checked);
+ }
+
+ $scope.receive_next = function () {
+ var list = [];
+ var next_per_stream = {};
+ angular.forEach(egSerialsCoreSvc.itemTree, function (item) {
+ if (next_per_stream[item.stream().id()]) return;
+ if (item.status() == 'Expected') {
+ next_per_stream[item.stream().id()] = item;
+ list.push(egCore.idl.Clone(item));
+ }
+ });
+
+ return egSerialsCoreSvc.process_items('receive', $scope.bibId, list, $scope.receive_and_barcode, false, $scope.do_print_routing_lists, function(){reload($scope.ssubId,_paging_filter)});
+ }
+
+ $scope.receive_selected = function (list) {
+ var items = list.filter(function(i){
+ return i.status == 'Expected';
+ });
+ return egSerialsCoreSvc.process_items('receive', $scope.bibId, items.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), $scope.receive_and_barcode, false, $scope.do_print_routing_lists, function(){reload($scope.ssubId,_paging_filter)});
+ }
+
+ $scope.reset_selected = function (list) {
+ return egSerialsCoreSvc.process_items('reset', $scope.bibId, list.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), false, false, false, function(){reload($scope.ssubId,_paging_filter)});
+ }
+
+ $scope.bind_selected = function (list) {
+ return egSerialsCoreSvc.process_items('bind', $scope.bibId, list.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), true, true, $scope.do_print_routing_lists, function(){reload($scope.ssubId,_paging_filter)});
+ }
+
+ $scope.set_selected_as_claimed = function(list) {
+ return egSerialsCoreSvc.set_item_status('Claimed', $scope.bibId, list.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), function(){reload($scope.ssubId,_paging_filter)});
+ }
+ $scope.set_selected_as_discarded = function(list) {
+ return egSerialsCoreSvc.set_item_status('Discarded', $scope.bibId, list.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), function(){reload($scope.ssubId,_paging_filter)});
+ }
+ $scope.set_selected_as_not_published = function(list) {
+ return egSerialsCoreSvc.set_item_status('Not Published', $scope.bibId, list.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), function(){reload($scope.ssubId,_paging_filter)});
+ }
+ $scope.set_selected_as_not_held = function(list) {
+ return egSerialsCoreSvc.set_item_status('Not Held', $scope.bibId, list.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ }), function(){reload($scope.ssubId,_paging_filter)});
+ }
+
+ $scope.menu_print_routing_lists = function (items) {
+ items = items.map(function(item) {
+ return egCore.idl.Clone(egSerialsCoreSvc.itemMap[item.id]);
+ });
+ return egSerialsCoreSvc.print_routing_lists($scope.bibId, items, false, true, $scope.do_print_routing_lists);
+ }
+
+ $scope.add_issuances = function () {
+ egSerialsCoreSvc.add_issuances($scope.ssubId).then(function() {
+ return reload($scope.ssubId,_paging_filter);
+ });
+ }
+
+ $scope.need_one_selected = function() {
+ var items = $scope.itemGridControls.selectedItems();
+ if (items.length == 1) return false;
+ return true;
+ };
+
+ $scope.need_many_selected = function() {
+ var items = $scope.itemGridControls.selectedItems();
+ if (items.length > 1) return false;
+ return true;
+ };
+
+ $scope.need_expected = function() {
+ var items = $scope.itemGridControls.selectedItems().filter(function(i){
+ return i.status == 'Expected';
+ });
+ if (items.length) return false;
+ return true;
+ };
+
+ $scope.item_notes = function(rows) {
+ return $scope.notes('item',rows);
+ }
+ // TODO - refactor this, it's duplicated in subscription_manager.js
+ $scope.notes = function(note_type,rows) {
+ if (!rows) { return; }
+
+ function modal(existing_notes) {
+ $uibModal.open({
+ templateUrl: './serials/t_notes',
+ animation: true,
+ controller: 'NotesCtrl',
+ resolve : {
+ note_type : function() { return note_type; },
+ rows : function() {
+ return rows;
+ },
+ notes : function() {
+ return existing_notes;
+ }
+ },
+ windowClass: 'app-modal-window',
+ backdrop: 'static',
+ keyboard: false
+ }).result.then(function(notes) {
+ egCore.pcrud.apply(notes).then(
+ function(a) { ngToast.success(egCore.strings.SERIALS_ITEM_NOTE_SUCCESS_SAVE) },
+ function(a) { ngToast.danger(egCore.strings.SERIALS_ITEM_NOTE_FAIL_SAVE) }
+ );
+ });
+ }
+
+ if (rows.length == 1) {
+ var fm_hint;
+ var search_hash = {};
+ var search_opt = {};
+ switch(note_type) {
+ case 'subscription':
+ fm_hint = 'ssubn';
+ search_hash.subscription = rows[0]['id'];
+ search_opt.order_by = { ssubn : 'create_date' };
+ break;
+ case 'distribution':
+ fm_hint = 'sdistn';
+ search_hash.distribution = rows[0]['sdist.id'];
+ search_opt.order_by = { sdistn : 'create_date' };
+ break;
+ case 'item': default:
+ fm_hint = 'sin';
+ search_hash.item = rows[0]['id'];
+ search_opt.order_by = { sin : 'create_date' };
+ break;
+ }
+ egCore.pcrud.search(fm_hint, search_hash, search_opt,
+ { atomic : true }
+ ).then(function(list) {
+ modal(list);
+ });
+ } else {
+ // support batch creation of notes across selections,
+ // but not editing
+ modal([]);
+ }
+ }
+
+}]
+
+ }
+})
+
+// TODO - refactor this; it's duplicated in subscription_manager.js
+.controller('NotesCtrl',
+ ['$scope','$uibModalInstance','egCore','note_type','rows','notes',
+function($scope , $uibModalInstance , egCore , note_type , rows , notes ) {
+ $scope.note_type = note_type;
+ $scope.focusNote = true;
+ $scope.note = {
+ creator : egCore.auth.user().id(),
+ title : '',
+ value : '',
+ pub : false,
+ 'alert' : false,
+ };
+
+ $scope.require_initials = false;
+ egCore.org.settings([
+ 'ui.staff.require_initials.copy_notes'
+ ]).then(function(set) {
+ $scope.require_initials = Boolean(set['ui.staff.require_initials.copy_notes']);
+ });
+
+ $scope.note_list = notes;
+
+ $scope.ok = function(note) {
+
+ var return_notes = [];
+ if (note.initials) note.value += ' [' + note.initials + ']';
+ if ( (typeof note.title != 'undefined' && note.title != '')
+ || (typeof note.value != 'undefined' && note.value != '')) {
+ angular.forEach(rows, function (r) {
+ var n;
+ switch(note_type) {
+ case 'subscription':
+ n = new egCore.idl.ssubn();
+ n.subscription(r['id']);
+ break;
+ case 'distribution':
+ n = new egCore.idl.sdistn();
+ n.distribution(r['sdist.id']);
+ break;
+ case 'item':
+ default:
+ n = new egCore.idl.sin();
+ n.item(r['id']);
+ }
+ n.isnew(true);
+ n.creator(note.creator);
+ n.pub(note.pub);
+ n['alert'](note['alert']);
+ n.title(note.title);
+ n.value(note.value);
+ return_notes.push( n );
+ });
+ }
+ angular.forEach(notes, function(n) {
+ if (n.ischanged() || n.isdeleted()) {
+ return_notes.push( n );
+ }
+ });
+ $uibModalInstance.close(return_notes);
+ }
+
+ $scope.cancel = function($event) {
+ $uibModalInstance.dismiss();
+ $event.preventDefault();
+ }
+}])
--- /dev/null
+angular.module('egSerialsMod', ['egCoreMod'])
+.factory('egSerialsCoreSvc',
+ ['egCore','orderByFilter','$q','$filter','$uibModal','ngToast','egConfirmDialog',
+function(egCore , orderByFilter , $q , $filter , $uibModal , ngToast , egConfirmDialog) {
+ var DAY = 86400000;
+ var service = {
+ bibId : null,
+ subId : null,
+ subTree : [],
+ subList : [],
+ sptList : [],
+ mfhdList : [],
+ potentialPatternList : [],
+ flatMfhdList : [],
+ itemMap : {},
+ itemTree : [],
+ itemList : [],
+ freq_offset : {
+ a : 365 * DAY,
+ b : 62 * DAY,
+ c : 4 * DAY,
+ d : DAY,
+ e : 14 * DAY,
+ f : 186 * DAY,
+ g : 2 * 365 * DAY,
+ h : 3 * 365 * DAY,
+ i : 2 * DAY,
+ j : 10 * DAY,
+ k : DAY,
+ m : 31 * DAY,
+ q : 93 * DAY,
+ s : 14 * DAY,
+ t : 124 * DAY,
+ w : 7 * DAY,
+ x : 0
+ },
+ freq_chrons : {
+ a : ['year'],
+ b : ['year','month'],
+ c : ['year','month'],
+ d : ['year','month','day'],
+ e : ['year','month','day'],
+ f : ['year','month'],
+ g : ['year'],
+ h : ['year','month'],
+ i : ['year','month','day'],
+ j : ['year','month','day'],
+ k : ['year','month','day'],
+ m : ['year','month'],
+ q : ['year','season'],
+ s : ['year','month'],
+ t : ['year','month','day'],
+ w : ['year','month','day'],
+ x : ['year','month','day']
+ },
+ get_chron_part : {
+ year : function(d) { return d.getFullYear() },
+ season: function(d) { return _loose_season(d) },
+ month : function(d) { return ('00' + (d.getMonth() + 1)).slice(-2) },
+ week : function(d) { return $filter('date')(d, 'ww') },
+ day : function(d) { return ('00'+d.getDate()).slice(-2) },
+ hour : function(d) { return ('00'+d.getHours()).slice(-2) }
+ },
+ item_status_list : [
+ 'Expected',
+ 'Received',
+ 'Claimed',
+ 'Bindery',
+ 'Bound',
+ 'Discarded',
+ 'Not Held',
+ 'Not Published'
+ ],
+ item_status_i18n : []
+ };
+
+ angular.forEach(service.item_status_list, function(status) {
+ service.item_status_i18n.push({
+ name : status,
+ label : egCore.strings.SERIALS_ITEM_STATUS[status]
+ });
+ });
+
+ function _loose_season(D) {
+ var m = D.getMonth() + 1;
+ var d = D.getDate();
+
+ if (
+ (m == 1 || m == 2) || (m == 12 && d >= 21) || (m == 3 && d < 20)
+ ) {
+ return 24; /* MFHD winter */
+ } else if (
+ (m == 4 || m == 5) || (m == 3 && d >= 20) || (m == 6 && d < 21)
+ ) {
+ return 21; /* spring */
+ } else if (
+ (m == 7 || m == 8) || (m == 6 && d >= 21) || (m == 9 && d < 22)
+ ) {
+ return 22; /* summer */
+ } else {
+ return 23; /* autumn */
+ }
+ }
+
+ service.fetch_mfhds = function(bibId, contextOrg) {
+ // TODO filter by contextOrg
+ return egCore.pcrud.search('sre', {
+ record : bibId,
+ deleted : 'f',
+ active : 't'
+ }, {
+ flesh : 3,
+ flesh_fields : {
+ 'sre' : ['owning_lib']
+ }
+ },
+ { atomic : true }
+ ).then(function(list) {
+ service.bibId = bibId;
+ service.mfhdList = list;
+ update_flat_mfhd_list();
+ });
+ }
+
+ service.fetch_patterns_from_bibs_mfhds = function(bibId) {
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.caption_and_pattern.find_legacy_by_bib_record.atomic',
+ egCore.auth.token(),
+ bibId
+ ).then(function(list) {
+ service.potentialPatternList = egCore.idl.toTypedHash(list);
+ angular.forEach(service.potentialPatternList, function(pot) {
+ var rec = new MARC21.Record({ marcxml : pot.marc });
+ var pattern_fields = rec.fields.filter(function(f) {
+ return (f.tag == '853' || f.tag == '854' || f.tag == '855');
+ });
+ pot.desc = '';
+ if (pattern_fields.length > 0) {
+ // just take the first one
+ var fld = pattern_fields[0];
+ pot.desc = fld.tag + ' ' + fld.ind1 + fld.ind2 +
+ fld.subfields.map(function(sf) {
+ return '$' + sf[0] + sf[1]
+ }).join('');
+ }
+ });
+ })
+ }
+
+ // fetch subscription, distributions, streams, captions,
+ // and notes associated with the indicated bib
+ service.fetch = function(bibId, contextOrg) {
+
+ var filter = { record_entry : bibId };
+ if (contextOrg) filter.owning_lib = egCore.org.descendants(contextOrg, true);
+ return egCore.pcrud.search('ssub', filter,
+ {
+ flesh : 5,
+ flesh_fields : {
+ 'ssub' : ['owning_lib','distributions', 'scaps', 'notes'],
+ 'sdist' : [ 'record_entry','holding_lib',
+ 'receive_call_number',
+ 'receive_unit_template',
+ 'bind_call_number',
+ 'bind_unit_template',
+ 'streams','notes'],
+ 'sstr' : ['routing_list_users'],
+ 'srlu' : ['reader'],
+ 'au' : ['card','home_ou','mailing_address','billing_address']
+ }
+ },
+ { atomic : true }
+ ).then(function(list) {
+ service.bibId = bibId;
+ service.subTree = list;
+ update_flat_sdist_sstr_list();
+ return $q.when(list);
+ });
+ }
+
+ // fetch subscription, distributions, streams, captions,
+ // and notes associated with the indicated bib
+ service.fetchLastCallnumber = function(contextOrg) {
+ return egCore.pcrud.search('acn', {
+ record : service.bibId,
+ owning_lib : contextOrg,
+ deleted : 'f'
+ }, { flesh : 1,
+ flesh_fields : {acn : ['prefix','suffix']},
+ order_by : [{class:'acn',field:'create_date',direction:'desc'}],
+ limit : 1
+ }, { atomic : true }
+ ).then(function(list) {
+ return $q.when(list[0]);
+ });
+ }
+
+ service.fetchItemsForSubPaged = function(subId,filter,offset,limit,sort) {
+ return service.fetchItemsForSub(
+ subId,
+ filter,
+ { limit : limit, offset : offset, paging : true },
+ sort
+ );
+ }
+
+ // Creates an inverted tree from item to sub
+ service.fetchItemsForSub = function(subId,filter,options,sort) {
+ var deferred = $q.defer(); // side-effects only, otherwise the grid is wonky
+
+ if (!filter) filter = {};
+ if (!options) options = { limit : 100 }; // only used during full refresh
+
+ if (!subId && service.subId) subId = service.subId;
+ if (!subId) return $q.reject('fetchItemsForSub: no subscription id');
+
+ var sub = service.get_ssub(subId);
+ if (!sub) return $q.reject('fetchItemsForSub: unknown subscription id');
+
+ var streams = [];
+ angular.forEach(sub.distributions(), function(dist) {
+ angular.forEach(
+ dist.streams().map(
+ function (stream) { return stream.id() }
+ ),
+ function (sid) { streams.push(sid) }
+ );
+ });
+
+ angular.extend(filter, {stream:streams});
+ angular.extend(options, {
+ order_by : [{class:'sitem',field:'date_expected'}], // best aprox of pub date
+ flesh : 1,
+ flesh_fields : {
+ sitem : ['notes','issuance','editor','creator','unit','url']
+ }
+ });
+ if (sort) {
+ angular.extend(options, {
+ order_by : [sort]
+ });
+ }
+
+ egCore.pcrud.search(
+ 'sitem', filter, options,
+ { atomic : true }
+ ).then(function(list) {
+ service.subId = subId;
+ if (!options.paging) { // not paged
+ service.itemTree = list;
+ service.itemMap = {};
+ } else { // paged
+ angular.forEach(list, function (item) {
+ var exists = service.itemTree.filter(function (i) {
+ return i.id() == item.id()
+ }).length;
+ if (!exists) service.itemTree.push(item);
+ });
+ }
+
+ // map items by stream for faster lookup
+ var tmp = {};
+ angular.forEach(list, function(item) {
+ if (!tmp[item.stream()]) tmp[item.stream()] = [];
+ tmp[item.stream()].push(item);
+ service.itemMap[item.id()] = item;
+ });
+
+ angular.forEach(sub.distributions(), function(dist) {
+ angular.forEach(dist.streams(), function(stream) {
+ angular.forEach(tmp[stream.id()], function (item) {
+ var routing_list = egCore.idl.Clone(stream.routing_list_users());
+ var st = egCore.idl.Clone(stream,1);
+ st.routing_list_users(routing_list);
+ var d = egCore.idl.Clone(dist,1);
+ var ss = egCore.idl.Clone(sub,1);
+ ss.distributions([]);
+ d.subscription(ss);
+ d.streams([]);
+ st.distribution(d);
+ item.stream(st);
+ });
+ });
+ });
+
+ var hashList = egCore.idl.toHash(service.itemTree);
+ angular.forEach(hashList, function (item) {
+ item['issuance.date_published'] = item.issuance.date_published;
+ item['stream.distribution.holding_lib.name'] = item.stream.distribution.holding_lib.name;
+ });
+
+ // ... then sort it
+ if (sort) {
+ service.itemList = hashList;
+ } else {
+ service.itemList = orderByFilter(hashList, ['"issuance.date_published"', '"stream.distribution.holding_lib.name"', '"id"']);
+ }
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ }
+
+ service.prep_new_holding_code = function (args) {
+
+ var type = args.type;
+ var date = args.date;
+ var prev_iss = args.prev_iss;
+ var curr_iss = args.curr_iss;
+ var adhoc = false;
+ var link = '1.1';
+ var current_values = {};
+
+ var sub = service.get_ssub(service.subId);
+ if (!sub) return args;
+
+ var scap;
+ if (prev_iss && prev_iss.holding_code()) { // we're predicting
+ var old_link_parts = JSON.parse(prev_iss.holding_code())[3].split('.');
+ var olink = old_link_parts[0];
+ var oseq = parseInt(old_link_parts[1]) + 1;
+ link = [olink,oseq].join('.');
+
+ if (prev_iss.holding_type())
+ type = prev_iss.holding_type();
+
+ if (prev_iss.caption_and_pattern()) {
+ var tmp = sub.scaps().filter(function (s) {
+ return (s.id() == prev_iss.caption_and_pattern());
+ });
+ if (angular.isArray(tmp) && tmp[0]) scap = tmp[0];
+ }
+
+ date = new Date(prev_iss.date_published());
+ } else if (curr_iss) { // we're editing
+ if (curr_iss.holding_type())
+ type = curr_iss.holding_type();
+
+ if (curr_iss.caption_and_pattern()) {
+ var tmp = sub.scaps().filter(function (s) {
+ return (s.id() == curr_iss.caption_and_pattern());
+ });
+ if (angular.isArray(tmp) && tmp[0]) scap = tmp[0];
+ }
+ if (!curr_iss.holding_code()) {
+ adhoc = true;
+ } else {
+ var tmp = JSON.parse(curr_iss.holding_code());
+ for (var i = 2; i < tmp.length; i += 2) {
+ // we're intentionally being a bit sloppy here, as
+ // the only subfields we are about in this context
+ // are the ones that are not repeatable
+ current_values[tmp[i]] = tmp[i + 1];
+ }
+ }
+
+ date = new Date(curr_iss.date_published());
+ } else {
+ // starting from scratch, so default the
+ // first publication date to the subscription start date
+ if (!date) date = new Date(sub.start_date());
+ }
+
+ args.date = date;
+
+ if (!scap) {
+ var tmp = sub.scaps().filter(function (s) {
+ return (s.type() == type && s.active() == 't');
+ });
+ if (angular.isArray(tmp) && tmp[0]) scap = tmp[0];
+ }
+
+ if (!scap) return args;
+
+ var others = [], enums = [], chrons = [], freq = '';
+ var pat = JSON.parse(scap.pattern_code()).slice(4); // just the part we care about
+
+ var freq_index = pat.indexOf('w');
+ if (freq_index > -1) {
+ freq = pat[freq_index + 1];
+ if (prev_iss) {
+ date = new Date(
+ date.getTime() + service.freq_offset[freq]
+ );
+ }
+ }
+
+ if (!date) date = new Date();
+
+ for (var i = 0; i < pat.length; i++) {
+ sf = pat[i]; i++;
+ val = pat[i];
+
+ if (sf != 'w') {
+ var pat_part = {
+ subfield : sf,
+ pattern : val
+ };
+
+ var chron_part = String(val).replace(/[)(]+/g,'');
+ if (sf in current_values) {
+ pat_part.value = current_values[sf];
+ } else {
+ try {
+ pat_part.value = service.get_chron_part[chron_part](date);
+ } catch (e) {
+ // not a chron part
+ pat_part.value = '';
+ }
+ }
+
+ if (sf.match(/[a-f]/)) {
+ enums.push(pat_part);
+ } else if (sf.match(/[i-l]/)) {
+ chrons.push(pat_part);
+ } else {
+ others.push(pat_part);
+ }
+ }
+ }
+
+ if (enums.length == 0 && chrons.length == 0) {
+ var parts = service.freq_chrons[freq];
+ if (parts.length) {
+ angular.forEach(parts, function(p, ind) {
+ var sf = !ind ? 'i' : !--ind ? 'j' : 'k';
+ chrons.push({
+ subfield : sf,
+ value : service.get_chron_part.year(date)
+ });
+ });
+ } else {
+ chrons = [
+ { subfield : 'i', value : service.get_chron_part.year(date) },
+ { subfield : 'j', value : service.get_chron_part.month(date) },
+ { subfield : 'k', value : service.get_chron_part.day(date) }
+ ];
+ }
+ }
+
+ return {
+ holding_code : ["4","1","8",link],
+ scap : scap.id(),
+ type : type,
+ date : date,
+ enums : enums,
+ chrons : chrons,
+ others : others,
+ freq : freq,
+ adhoc : adhoc
+ };
+ }
+
+ service.new_holding_code = function (options) {
+ if (options === undefined) options = {};
+ options.count = options.count || 1;
+ options.label = options.label || '';
+
+ return $uibModal.open({
+ templateUrl: './serials/t_holding_code_dialog',
+ //size: 'lg',
+ //windowClass: 'eg-wide-modal',
+ backdrop: 'static',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ $scope.focusMe = true;
+ $scope.title = options.title;
+ $scope.request_count = options.request_count;
+ $scope.count = options.count;
+ $scope.label = options.label;
+ $scope.save_label = options.save_label;
+ $scope.pubdate = options.date;
+ $scope.type = options.type || 'basic';
+ $scope.args = { adhoc : false };
+ if (options.adhoc) $scope.args.adhoc = true;
+ $scope.can_change_adhoc = options.can_change_adhoc;
+
+ function refresh (n,o) {
+ if (n && o && n !== o) {
+ $scope.args = service.prep_new_holding_code({
+ type : $scope.type,
+ date : $scope.pubdate,
+ prev_iss : options.prev_iss,
+ curr_iss : options.curr_iss,
+ });
+ if (!options.can_change_adhoc && options.adhoc) $scope.args.adhoc = true;
+
+ if ($scope.args.type && $scope.type != $scope.args.type)
+ $scope.type = $scope.args.type;
+ if ($scope.args.date)
+ $scope.pubdate = $scope.args.date;
+
+ delete options.prev_iss; // only use this once
+ delete options.curr_iss; // only use this once
+ }
+ }
+
+ $scope.$watch('count',function (n) {options.count = n});
+ $scope.$watch('label',function (n) {options.label = n});
+ $scope.$watch('type',refresh);
+ $scope.$watch('pubdate',refresh);
+
+ $scope.ok = function(args) { $uibModalInstance.close(args) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+
+ refresh(1,2); // force data loading
+ }]
+ }).result.then(function (args) {
+ if (args.enums && args.chrons) {
+ angular.forEach(
+ args.enums.concat(args.chrons),
+ function (e) {
+ args.holding_code.push(e.subfield);
+ args.holding_code.push(e.value);
+ }
+ );
+ }
+ args.count = options.count;
+ args.label = options.label;
+ return $q.when(args);
+ });
+ }
+
+ function update_flat_mfhd_list() {
+ var list = [];
+ angular.forEach(service.mfhdList, function(sre) {
+ var mfhdHash = egCore.idl.toHash(sre);
+ var rec = new MARC21.Record({ marcxml : mfhdHash.marc });
+ var _mfhd = {
+ 'id' : mfhdHash.id,
+ 'owning_lib.name' : mfhdHash.owning_lib.name,
+ 'owning_lib.id' : mfhdHash.owning_lib.id,
+ 'marc' : rec.toBreaker(),
+ 'marc_xml' : mfhdHash.marc,
+ 'svr' : null,
+ 'basic_holdings' : null,
+ 'index_holdings' : null,
+ 'supplement_holdings' : null
+ }
+ list.push(_mfhd);
+ egCore.net.request(
+ 'open-ils.search',
+ 'open-ils.search.serial.record.mfhd.retrieve',
+ mfhdHash.id
+ ).then(function(svr) {
+ _mfhd.svr = egCore.idl.toTypedHash(svr);
+ _mfhd.basic_holdings = _mfhd.svr.basic_holdings.join("; ");
+ _mfhd.index_holdings = _mfhd.svr.index_holdings.join("; ");
+ _mfhd.supplement_holdings = _mfhd.svr.supplement_holdings.join("; ");
+ })
+ });
+ service.flatMfhdList.length = 0;
+ angular.extend(service.flatMfhdList, list);
+ }
+
+ // create/update a flat version of the subscription/distribution/stream
+ // tree for feeding to the distribution and stream grid
+ function update_flat_sdist_sstr_list() {
+
+ // flatten the structure...
+ var list = [];
+ angular.forEach(service.subTree, function(ssub) {
+ var ssubHash = egCore.idl.toHash(ssub);
+
+ var _ssub = {
+ 'id' : ssubHash.id,
+ 'owning_lib.name' : ssubHash.owning_lib.name,
+ 'owning_lib.id' : ssubHash.owning_lib.id,
+ 'start_date' : ssubHash.start_date,
+ 'end_date' : ssubHash.end_date,
+ 'expected_date_offset' : ssubHash.expected_date_offset
+ };
+ // insert and escape if we have no distributions
+ if (ssubHash.distributions.length == 0) {
+ list.push(_ssub);
+ return;
+ }
+
+ angular.forEach(ssubHash.distributions, function(sdist) {
+ var _sdist = {};
+ angular.forEach([
+ 'id',
+ 'summary_method',
+ 'record_entry',
+ 'label',
+ 'display_grouping',
+ 'unit_label_prefix',
+ 'unit_label_suffix',
+ ], function(fld) {
+ _sdist['sdist.' + fld] = sdist[fld];
+ });
+ _sdist['sdist.holding_lib.name'] = sdist.holding_lib.name;
+ _sdist['sdist.holding_lib.id'] = sdist.holding_lib.id;
+ _sdist['sdist.receive_call_number.label'] =
+ sdist.receive_call_number ? sdist.receive_call_number.label : null;
+ _sdist['sdist.receive_unit_template.name'] =
+ sdist.receive_unit_template ? sdist.receive_unit_template.name : null;
+ _sdist['sdist.bind_call_number.label'] =
+ sdist.bind_call_number ? sdist.bind_call_number.label : null;
+ _sdist['sdist.bind_unit_template.name'] =
+ sdist.bind_unit_template ? sdist.bind_unit_template.name : null;
+ // if we have no streams, add to the list and escape
+ if (sdist.streams.length == 0) {
+ var row = {};
+ angular.extend(row, _ssub, _sdist);
+ list.push(row);
+ return;
+ }
+
+ angular.forEach(sdist.streams, function(sstr) {
+ var _sstr = {
+ 'sstr.id' : sstr.id,
+ 'sstr.routing_label' : sstr.routing_label,
+ 'sstr.additional_routing' : ((sstr.routing_list_users.length > 0) ? true : false)
+ };
+ var row = {};
+ angular.extend(row, _ssub, _sdist, _sstr);
+ list.push(row);
+ });
+ });
+ });
+
+ // ... then sort it
+ service.subList.length = 0;
+ angular.extend(service.subList,
+ orderByFilter(list, ['"owning_lib.name"', '"start_date"', '"end_date"',
+ '"holding_lib.name"', '"sdist.id"', '"sstr.id"'])
+ );
+
+ // ... then remove duplication of owning library, distribution library,
+ // and distribution labels
+ var sub_lib = null;
+ var dist_lib = null;
+ var dist_label = null;
+ var index = 0;
+ angular.forEach(service.subList, function(row) {
+ row['index'] = index++;
+ if (sub_lib == row['owning_lib.name']) {
+ row['owning_lib.name'] = null;
+ } else {
+ sub_lib = row['owning_lib.name'];
+ dist_lib = row['sdist.holding_lib.name'];
+ dist_label = row['sdist.label'];
+ return;
+ }
+ if (dist_lib == row['sdist.holding_lib.name']) {
+ row['sdist.holding_lib.name'] = null;
+ } else {
+ dist_lib = row['sdist.holding_lib.name'];
+ }
+ if (dist_label == row['sdist.label']) {
+ row['sdist.label'] = null;
+ } else {
+ dist_label = row['sdist.label'];
+ }
+ });
+ }
+
+ // verify that a subscription ID and bib ID are actually
+ // associated with each other
+ service.verify_subscription_id = function(bibId, ssubId) {
+ var deferred = $q.defer();
+ egCore.pcrud.search('ssub', {
+ record_entry : bibId,
+ id : ssubId
+ }, {}, { atomic : true, idlist : true }
+ ).then(function(list) {
+ if (list.length == 1) {
+ deferred.resolve(true);
+ } else {
+ deferred.resolve(false);
+ }
+ });
+ return deferred.promise;
+ }
+
+ service.get_ssub = function(ssubId) {
+ if (!ssubId) return;
+ for (var i = 0; i <= service.subTree.length; i++) {
+ if (service.subTree[i].id() == ssubId) {
+ return service.subTree[i];
+ }
+ }
+ }
+
+ service.fetch_spt = function() {
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.pattern_template.retrieve.at.atomic',
+ egCore.auth.token(),
+ egCore.auth.user().ws_ou()
+ ).then(function(list) {
+ service.sptList.length = 0;
+ angular.extend(service.sptList, list);
+ });
+ }
+
+ service.fetch_templates = function(org) {
+ return egCore.pcrud.search('act',
+ {owning_lib : egCore.org.fullPath(org, true)},
+ {order_by : { act : 'name' }}, {atomic : true}
+ );
+ };
+
+ service.print_routing_lists = function (bibId, items, check, force, print_rl) {
+ if (!check && !print_rl && !force) return $q.when();
+
+ return egCore.net.request(
+ 'open-ils.search',
+ 'open-ils.search.biblio.record.mods_slim.retrieve',
+ bibId
+ ).then(function(mvr) {
+
+ var by_issuance = {};
+ angular.forEach(items, function (i) {
+ if (check && !i._print_routing_list) return;
+ if (!by_issuance[i.issuance().id()])
+ by_issuance[i.issuance().id()] = [];
+ by_issuance[i.issuance().id()].push(i);
+ });
+
+ var issuance_matrix = [];
+ angular.forEach(by_issuance, function (list) {
+ issuance_matrix.push(list);
+ });
+
+ var deferred = $q.defer();
+ var promise = deferred.promise;
+
+ angular.forEach(issuance_matrix, function(item_list, index) {
+
+ promise = promise.then(function(){
+ return $uibModal.open({
+ templateUrl: './serials/t_print_routing_list',
+ size: 'lg',
+ windowClass: 'eg-wide-modal',
+ backdrop: 'static',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ var all_users = [];
+ var all_streams = [];
+
+ angular.forEach(item_list, function(i){
+ all_streams.push(i.stream());
+ all_users = all_users.concat(i.stream().routing_list_users());
+ });
+
+ $scope.xulg = {
+ show_print_button: true,
+ routing_list_data: {
+ streams : all_streams,
+ mvr : mvr,
+ issuance: item_list[0].issuance(),
+ users : orderByFilter(all_users, 'pos')
+ }
+ };
+
+ $scope.url = '/eg/serial/print_routing_list_users?ses=' + egCore.auth.token();
+ $scope.last = index == issuance_matrix.length - 1 ? true : false;
+ $scope.ok = function() { $uibModalInstance.close() }
+ }]
+ }).result;
+ });
+
+ });
+
+ return deferred.resolve();
+ });
+
+ }
+
+ service.set_item_status = function(newStatus, bibId, list, callback) {
+ if (!callback) callback = function () { return $q.when() }
+ if (!list.length) return $q.reject();
+
+ return egConfirmDialog.open(
+ egCore.strings.CONFIRM_CHANGE_ITEMS.status,
+ egCore.strings.CONFIRM_CHANGE_ITEMS_MESSAGE.status,
+ {items : list.length}
+ ).result.then(function () {
+ var promises = [$q.when()];
+ angular.forEach(list, function(item) {
+ item.status(newStatus);
+ promises.push(
+ egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.item.update',
+ egCore.auth.token(),
+ item
+ ).then(function(res) {
+ return $q.when();
+ })
+ );
+ });
+ $q.all(promises).then(function() {
+ callback();
+ });
+ });
+ }
+
+ service.process_items = function (mode, bibId, list, do_barcode, bind, print_rl, callback) {
+ if (!callback) callback = function () { return $q.when() }
+ if (!list.length) return $q.reject();
+
+ // deal with locations and circ mods for *NEW* units
+ var copy_locations = {};
+ var circ_mods = {};
+
+ // deal with barcodes and call numbers for *NEW* units
+ var barcodes = {};
+ var call_numbers = {};
+ var call_numbers_by_siss_and_sdist = {};
+
+ var deferred = $q.defer();
+ var current_promise = deferred.promise;
+ var last_promise;
+
+ var sitem_alerts = [];
+ var sdist_alerts = [];
+ var ssub_alerts = list[0].stream().distribution().subscription().notes().filter(function(n){
+ return n.alert() == 't';
+ })
+
+ var dist_seen = {};
+ angular.forEach(list, function(i) {
+ sitem_alerts = sitem_alerts.concat(
+ i.notes().filter(function(n){
+ return n.alert() == 't';
+ })
+ );
+ var sdist = '_'+i.stream().distribution().id();
+ if (!dist_seen[sdist]) {
+ dist_seen[sdist] = 1;
+ sdist_alerts = sdist_alerts.concat(
+ i.stream().distribution().notes().filter(function(n){
+ return n.alert() == 't';
+ })
+ );
+ }
+ });
+
+ if (do_barcode || bind) {
+
+ last_promise = current_promise.then(function(){ return $uibModal.open({
+ templateUrl: './serials/t_batch_receive',
+ size: 'lg',
+ windowClass: 'eg-wide-modal',
+ backdrop: 'static',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+
+ $scope.print_routing_lists = print_rl;
+ $scope.barcode_items = do_barcode;
+ $scope.force_bind = bind;
+ $scope.bind = bind;
+ $scope.items = list;
+ $scope.ssub_alerts = ssub_alerts;
+ $scope.sdist_alerts = sdist_alerts;
+ $scope.sitem_alerts = sitem_alerts;
+ $scope.acn_list = [];
+ $scope.acnp_labels = [];
+ $scope.acns_labels = [];
+ $scope.acpl_list = [];
+
+ $scope.cannot_print = function (index) {
+ return $scope.items[index].stream().routing_list_users().length == 0 || ($scope.bind && index > 0);
+ }
+
+ $scope.bind_or_none = function (index) {
+ return !$scope.barcode_items || ($scope.bind && index > 0);
+ }
+
+ $scope.focus_next_barcode = function (index) {
+ index++;
+ $('#item_barcode_'+index).focus().select();
+ }
+
+ $scope.fullCNLabel = function (cn) {
+ var label = [cn.prefix.label,cn.label,cn.suffix.label].join(' ');
+ return label;
+ }
+
+ $scope.apply_template_overrides = function (e) {
+ if ($scope.selected_call_number) {
+ angular.forEach($scope.items, function (i) {
+ i._call_number = $scope.selected_call_number.label;
+ i._cn_prefix = $scope.selected_call_number.prefix.label;
+ i._cn_suffix = $scope.selected_call_number.suffix.label;
+ });
+ }
+ if ($scope.selected_circ_mod) {
+ angular.forEach($scope.items, function (i) {
+ i._circ_mod = $scope.selected_circ_mod;
+ });
+ }
+ if ($scope.selected_copy_location) {
+ angular.forEach($scope.items, function (i) {
+ i._copy_location = $scope.selected_copy_location;
+ });
+ }
+ }
+
+ $scope.ok = function(items) { $uibModalInstance.close(items) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+
+ var dist_libs = {};
+ var pile_o_promises = [$q.when()];
+
+ // let's gather what we need...
+ angular.forEach(list, function (i, index) {
+ var dlib = i.stream().distribution().holding_lib().id();
+ dist_libs[dlib] = egCore.org.fullPath(dlib, true);
+ if (i.unit()) {
+ i._barcode = i.unit().barcode();
+ pile_o_promises.push(
+ egCore.pcrud.retrieve(
+ 'acn', i.unit().call_number(),
+ {flesh : 1, flesh_fields : {acn : ['prefix','suffix']}}
+ ).then(function(cn){
+ if (cn.deleted() == 'f') {
+ i._call_number = cn.label();
+ i._cn_prefix = cn.prefix().label();
+ i._cn_suffix = cn.suffix().label();
+ }
+ })
+ );
+ } else {
+ if (i.stream().distribution()[mode + '_call_number']() &&
+ i.stream().distribution()[mode + '_call_number']().deleted() == 'f'
+ ) {
+ i._call_number = i.stream().distribution()[mode + '_call_number']().label();
+ } else {
+ pile_o_promises.push(
+ service.fetchLastCallnumber(
+ i.stream().distribution().holding_lib().id()
+ ).then(function(cn){
+ if (cn) {
+ i._call_number = cn.label();
+ i._cn_prefix = cn.prefix().label();
+ i._cn_suffix = cn.suffix().label();
+ }
+ })
+ );
+ }
+ }
+
+ if (i.stream().distribution()[mode + '_unit_template']()) {
+ i._copy_location = i.stream().distribution()[mode + '_unit_template']().location();
+ i._circ_mod = i.stream().distribution()[mode + '_unit_template']().circ_modifier();
+ }
+
+ if ($scope.print_routing_lists && !$scope.cannot_print(index))
+ i._print_routing_list = true;
+
+ i._receive = true;
+ });
+
+ // build unique list of orgs from distribution.holding_lib fullPaths
+ var dist_lib_list = [];
+ angular.forEach(dist_libs, function (l) {
+ dist_lib_list = dist_lib_list.concat(l);
+ });
+ dist_lib_list = dist_lib_list.filter(function(v,i,s){
+ return s.indexOf(v) == i;
+ });
+
+ // Copy locations only come from the workstation location, same as XUL
+ pile_o_promises.push(egCore.pcrud.search(
+ 'acpl',
+ {owning_lib : egCore.org.fullPath(egCore.auth.user().ws_ou(), true)},
+ {},{ atomic : true }
+ ).then(function (list) {
+ $scope.acpl_list = list.map(function(i){return egCore.idl.toHash(i)});
+ return $q.when();
+ }));
+
+ // Call numbers, however, come from anywhere the distributions might live
+ pile_o_promises.push(egCore.pcrud.search(
+ 'acn',
+ {deleted : 'f', record : bibId, owning_lib : dist_lib_list},
+ {flesh : 1, flesh_fields : {acn : ['prefix','suffix']}},{ atomic : true }
+ ).then(function (list) {
+ $scope.acn_list = list.map(function(i){return egCore.idl.toHash(i)});
+ return $q.when();
+ }));
+
+ // Likewise for prefix and suffix, for combo box
+ angular.forEach(['acnp','acns'], function (cl) {
+ pile_o_promises.push(egCore.pcrud.search(
+ cl,
+ {owning_lib : dist_lib_list},
+ {},{ atomic : true }
+ ).then(function (list) {
+ $scope[cl+'_labels'] = list.map(function(i){return i.label()});
+ return $q.when();
+ }));
+ });
+
+ pile_o_promises.push(egCore.pcrud.retrieveAll(
+ 'ccm', {}, { atomic : true }
+ ).then(function (list) {
+ $scope.ccm_list = list.map(function(i){return egCore.idl.toHash(i)});
+ return $q.when();
+ }));
+
+ $q.all(pile_o_promises).then(function() {
+ console.log('receive data collected');
+ });
+
+ $scope.$watch('barcode_items', function (n,o) {
+ if (n === undefined || n == o) return;
+ do_barcode = n;
+ });
+
+ $scope.$watch('bind', function (n,o) {
+ if (n === undefined || n == o) return;
+ bind = n;
+ if (bind) {
+ angular.forEach($scope.items, function (i,index) {
+ if (index > 0) i._print_routing_list = false;
+ });
+ }
+ });
+
+ $scope.$watch('auto_barcodes', function (n,o) {
+ if (n === undefined || n == o) return;
+
+ var bc = '@@AUTO';
+ if (!n) bc = '';
+
+ angular.forEach($scope.items, function (i) {
+ if (!i.stream().distribution().receive_unit_template()) return;
+ var _barcode = i._barcode;
+ i._barcode = bc || i._old_barcode;
+ i._old_barcode = _barcode;
+ });
+ });
+
+ $scope.$watch('print_routing_lists', function (n,o) {
+ if (n === undefined || n == o) return;
+
+ angular.forEach($scope.items, function(i, index) {
+ if (!$scope.cannot_print(index)) {
+ i._print_routing_list = n;
+ } else {
+ i._print_routing_list = false;
+ }
+ });
+ });
+ }]
+ }).result});
+ } else {
+ last_promise = current_promise.then(function(){ return $uibModal.open({
+ templateUrl: './serials/t_receive_alerts',
+ controller:
+ ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
+ $scope.title = egCore.strings.CONFIRM_CHANGE_ITEMS[mode];
+ $scope.items = list.length;
+ $scope.list = list;
+ $scope.mode = mode;
+ $scope.ssub_alerts = ssub_alerts;
+ $scope.sdist_alerts = sdist_alerts;
+ $scope.sitem_alerts = sitem_alerts;
+
+ $scope.ok = function(items) { $uibModalInstance.close(items) }
+ $scope.cancel = function () { $uibModalInstance.dismiss() }
+ }]
+ }).result.then(
+ function(items) {
+ angular.forEach(list, function (i, index) {
+ i._receive = true;
+ });
+ return $q.when(list);
+ })
+ });
+ }
+
+ last_promise.then(function (items) {
+
+ var method;
+ if (mode == 'receive') {
+ method = 'open-ils.serial.receive_items';
+ items = items.filter(function(i){return i._receive});
+ } else if ( mode == 'bind') {
+ method = 'open-ils.serial.bind_items';
+ items = items.filter(function(i){return i._receive});
+ } else if ( mode == 'reset') {
+ method = 'open-ils.serial.reset_items';
+ }
+
+ if (!items.length) return $q.reject();
+
+ var donor_unit_ids = {};
+ angular.forEach(items, function(i, index) {
+ if (i.unit()) donor_unit_ids[i.unit().id()] = 1;
+ if (do_barcode) i.unit(-1);
+ if (bind) i.unit(-2);
+ copy_locations[i.id()] = i._copy_location;
+ circ_mods[i.id()] = i._circ_mod;
+ call_numbers[i.id()] = [i._cn_prefix, i._call_number, i._cn_suffix] || 'DEFAULT';
+ barcodes[i.id()] = i._barcode || '@@AUTO';
+ if (bind && index > 0) barcodes[i.id()] = items[0]._barcode;
+ });
+
+ return egCore.net.request(
+ 'open-ils.serial', method,
+ egCore.auth.token(), items, barcodes, call_numbers, donor_unit_ids,
+ {circ_mods:circ_mods, copy_locations : copy_locations}
+ ).then(
+ function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ return service.print_routing_lists(bibId, items, do_barcode || bind, false, print_rl)
+ .finally(callback);
+ }
+ },
+ function(resp) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ }
+ );
+ });
+
+ return deferred.resolve();
+ }
+
+ service.add_issuances = function (mySsubId) {
+ if (!mySsubId && service.subId) mySsubId = service.subId;
+ if (!mySsubId) return $q.reject('fetchItemsForSub: no subscription id');
+
+ var sub = service.get_ssub(mySsubId);
+ if (!sub) return $q.reject('fetchItemsForSub: unknown subscription id');
+
+ var streams = [];
+ angular.forEach(sub.distributions(), function(dist) {
+ angular.forEach(
+ dist.streams().map(
+ function (stream) { return stream.id() }
+ ),
+ function (sid) { streams.push(sid) }
+ );
+ });
+
+ var options = {
+ order_by : [{class:'sitem',field:'date_expected',direction:'desc'}], // best aprox of pub date
+ limit : 1,
+ flesh : 1,
+ flesh_fields : { sitem : ['issuance'] }
+ };
+
+ return egCore.pcrud.search(
+ 'sitem', {stream:streams},
+ { order_by : [{class:'sitem',field:'date_expected',direction:'desc'}], // best aprox of pub date
+ limit : 1,
+ flesh : 1,
+ flesh_fields : { sitem : ['issuance'] }
+ },
+ { atomic : true }
+ ).then(function(list) {
+ var lastItem = list[0];
+
+ if (lastItem) lastItem = lastItem.issuance();
+
+ return service.new_holding_code({
+ title : egCore.strings.SERIALS_ISSUANCE_PREDICT,
+ request_count : true,
+ prev_iss : lastItem,
+ allow_adhoc : false
+ }).then(function(hc) {
+
+ var base_iss;
+ if (!lastItem) {
+ base_iss = new egCore.idl.siss();
+ base_iss.creator( egCore.auth.user().id() );
+ base_iss.editor( egCore.auth.user().id() );
+ base_iss.date_published( hc.date.toISOString() );
+ base_iss.subscription( mySsubId );
+ base_iss.caption_and_pattern( hc.scap );
+ base_iss.holding_code( JSON.stringify(hc.holding_code) );
+ base_iss.holding_type( hc.type );
+ }
+
+ // if we're predicting without a preexisting holding, reduce the count
+ if (!lastItem) hc.count--;
+
+ return egCore.net.request(
+ 'open-ils.serial',
+ 'open-ils.serial.make_predictions',
+ egCore.auth.token(),
+ { ssub_id : mySsubId,
+ include_base_issuance : lastItem ? 0 : 1,
+ num_to_predict : hc.count,
+ base_issuance : base_iss || lastItem
+ }
+ ).then(
+ function(resp) {
+ var evt = egCore.evt.parse(resp);
+ if (evt) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ } else {
+ ngToast.success(egCore.strings.SERIALS_ISSUANCE_SUCCESS_SAVE);
+ }
+ },
+ function(resp) {
+ ngToast.danger(egCore.strings.SERIALS_ISSUANCE_FAIL_SAVE);
+ }
+ );
+ });
+ });
+ }
+
+ return service;
+}]);
+
--- /dev/null
+/**
+ * MFHD tools and directives.
+ */
+angular.module('egMfhdMod', ['egCoreMod', 'ui.bootstrap'])
+
+.factory('egMfhdCreateDialog',
+ ['$uibModal','egCore',
+function($uibModal , egCore) {
+ var service = {};
+
+ service.open = function(bibId, orgId) {
+ return $uibModal.open({
+ templateUrl: './share/t_mfhd_create_dialog',
+ controller: ['$scope', '$uibModalInstance',
+ function($scope, $uibModalInstance) {
+ $scope.mfhd_lib = orgId ?
+ egCore.org.get(orgId) :
+ null;
+ $scope.ok = function() {
+ egCore.net.request(
+ 'open-ils.cat',
+ 'open-ils.cat.serial.record.xml.create',
+ egCore.auth.token(),
+ 1, // source
+ $scope.mfhd_lib.id(),
+ bibId
+ ).then(function() {
+ $uibModalInstance.close()
+ });
+ }
+ $scope.cancel = function() {
+ $uibModalInstance.dismiss();
+ }
+ }
+ ]
+ })
+ }
+
+ return service;
+}
+])
scope: {
list: "=", // list of strings
selected: "=",
+ onSelect: "=",
egDisabled: "=",
allowAll: "@",
+ placeholder: "@",
focusMe: "=?"
},
template:
'<div class="input-group">'+
- '<input type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()" focus-me="focusMe">'+
+ '<input placeholder="{{placeholder}}" type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()" focus-me="focusMe">'+
'<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
- '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
+ '<button type="button" ng-click="showAll()" ng-disabled="egDisabled" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
'<ul class="dropdown-menu dropdown-menu-right">'+
- '<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
+ '<li ng-repeat="item in list|filter:selected:compare"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
'<li ng-if="complete_list" class="divider"><span></span></li>'+
'<li ng-if="complete_list" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
'</ul>'+
$scope.clickedopen = false;
$scope.clickedclosed = null;
+ $scope.compare = function (ex, act) {
+ if (act === null || act === undefined) return true;
+ if (act.toString) act = act.toString();
+ return new RegExp(act.toLowerCase()).test(ex)
+ }
+
$scope.showAll = function () {
$scope.clickedopen = !$scope.clickedopen;
$scope.clickedclosed = !$scope.clickedopen;
}
- if ($scope.selected.length > 0) $scope.complete_list = true;
- if ($scope.selected.length == 0) $scope.complete_list = false;
+ if ($scope.selected && $scope.selected.length > 0) $scope.complete_list = true;
+ if (!$scope.selected || $scope.selected.length == 0) $scope.complete_list = false;
$scope.makeOpen();
}
$scope.list,
$scope.selected
).length > 0 && $scope.selected.length > 0);
- if ($scope.clickedclosed) $scope.isopen = false;
+ if ($scope.clickedclosed) {
+ $scope.isopen = false;
+ $scope.clickedclosed = null;
+ }
}
$scope.changeValue = function (newVal) {
$scope.clickedclosed = null;
$scope.clickedopen = false;
if ($scope.selected.length == 0) $scope.complete_list = false;
+ if ($scope.onSelect) $scope.onSelect();
}
}