From: (no author) <(no author)@dcc99617-32d9-48b4-a31d-7c20da2025e4> Date: Wed, 2 May 2007 20:02:32 +0000 (+0000) Subject: This commit was manufactured by cvs2svn to create branch 'rel_1_0_5'. X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=0b6e7f58c1248ef44860ef7e019b5b3d41d7a996;p=Evergreen.git This commit was manufactured by cvs2svn to create branch 'rel_1_0_5'. git-svn-id: svn://svn.open-ils.org/ILS/branches/rel_1_0_5@7188 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Evergreen/conf/lib_ips.txt b/Evergreen/conf/lib_ips.txt index c2e8ab0a6c..6dff1e552e 100644 --- a/Evergreen/conf/lib_ips.txt +++ b/Evergreen/conf/lib_ips.txt @@ -398,6 +398,12 @@ STRL-SWAIN 168.13.107.1 168.13.107.254 SWGRL-DEC 168.13.49.1 168.13.49.254 SWGRL-MIL 168.13.50.1 168.13.50.254 SWGRL-SEM 168.13.51.1 168.13.51.254 +TCPLS-THOMAS 168.13.57.1 168.13.57.254 +TCPLS-MEIGS 168.13.55.1 168.13.55.254 +TCPLS-COOL 168.13.53.1 168.13.53.254 +TCPLS-BOS 168.13.52.1 168.13.52.254 +TCPLS-OCH 168.13.54.1 168.13.54.254 +TCPLS-PACO 168.13.56.1 168.13.56.254 TLLS-LS 168.13.161.1 168.13.161.254 TLLS-MV 168.13.162.1 168.13.162.254 TRRL-BRANT 168.13.113.1 168.13.113.254 diff --git a/Evergreen/src/extras/overdue_notice_email b/Evergreen/src/extras/overdue_notice_email index 8a4707c4c7..8d2e391fb0 100644 --- a/Evergreen/src/extras/overdue_notice_email +++ b/Evergreen/src/extras/overdue_notice_email @@ -1,6 +1,7 @@ To: ${EMAIL_RECIPIENT} From: ${EMAIL_SENDER} Reply-To: ${EMAIL_REPLY_TO} +Errors-To: ${EMAIL_ERRORS_TO} Subject: Overdue Materials Notification ${EMAIL_HEADERS} @@ -15,7 +16,7 @@ TITLE AUTHOR CALL NUMBER ${OVERDUE_ITEMS[ ${TITLE} : ${AUTHOR} ${CALL_NUMBER} -Due: ${DUE_DAY}/${DUE_MONTH}/${DUE_YEAR} ID: ${ITEM_BARCODE} +Due: ${DUE_MONTH}/${DUE_DAY}/${DUE_YEAR} ID: ${ITEM_BARCODE} ]} diff --git a/Evergreen/src/extras/report-fail b/Evergreen/src/extras/report-fail index df3f4d1a93..496da93949 100644 --- a/Evergreen/src/extras/report-fail +++ b/Evergreen/src/extras/report-fail @@ -1,16 +1,15 @@ To: {TO} From: {FROM} Reply-To: {REPLY_TO} -Subject: Report {REPORT_NAME} failed +Subject: Report Failure Notification -Your report, {REPORT_NAME}, schedued to run at {RUN_TIME} has failed with -the following error message: +Your report, named [{REPORT_NAME}], was scheduled to run at {RUN_TIME} has failed with the following error message: -{ERROR_TEXT} + {ERROR_TEXT} The SQL command attempted was: -{SQL} + {SQL} If you are unsure of the meaning of this message, please contact PINES staff and give them both the error message and the SQL command. diff --git a/Evergreen/src/extras/report-success b/Evergreen/src/extras/report-success index 17ee2e9af7..38212df0d4 100644 --- a/Evergreen/src/extras/report-success +++ b/Evergreen/src/extras/report-success @@ -1,12 +1,12 @@ To: {TO} From: {FROM} Reply-To: {REPLY_TO} -Subject: Report {REPORT_NAME} complete +Subject: Report Completion Notification -Your report, {REPORT_NAME}, schedued to run at {RUN_TIME} completed -at {COMPLETE_TIME} and is available for viewing a the following URL: +Your report, named [{REPORT_NAME}], was scheduled to run at {RUN_TIME} and completed at {COMPLETE_TIME}. +It is available for viewing at the following URL: -{OUTPUT_URL} + {OUTPUT_URL} If you have any general questions, please contact the PINES staff. diff --git a/Evergreen/src/support-scripts/eg_gen_overdue.pl b/Evergreen/src/support-scripts/eg_gen_overdue.pl index 9946d8345e..6b02865f19 100755 --- a/Evergreen/src/support-scripts/eg_gen_overdue.pl +++ b/Evergreen/src/support-scripts/eg_gen_overdue.pl @@ -181,6 +181,8 @@ sub print_notice { my @patron_data = fetch_patron_data($usr); my @org_data = fetch_org_data($org); + return unless (@patron_data and @org_data); + my $email; if( $email = $patron_data[0]->email @@ -303,8 +305,9 @@ sub fetch_org_data { } my $name = $org->name; - my $email = $org->email; my $phone = $org->phone; + my $email = $org->email; + my( $s1, $s2, $city, $state, $zip ); my $baddr = $org->billing_address || $org->mailing_address; @@ -435,12 +438,23 @@ sub send_email { my $r = ($range eq '7day') ? 7 : 14; - $org_email ||= $mail_sender; + # - default to the global sender for the errors-to header + my $errors_to = $mail_sender; + + # if they have an org setting for errors-to, use that as the errors-to address + if( my $set = $e->search_actor_org_unit_setting( + { name => 'org.bounced_emails', org_unit => $org->id } )->[0] ) { + + my $bemail = JSON->JSON2perl($set->value); + $errors_to = $bemail if $bemail; + } + $tmpl =~ s/\${EMAIL_RECIPIENT}/$pemail/; - $tmpl =~ s/\${EMAIL_SENDER}/$mail_sender/o; - $tmpl =~ s/\${EMAIL_REPLY_TO}/$org_email/; - $tmpl =~ s/\${EMAIL_HEADERS}//; + $tmpl =~ s/\${EMAIL_SENDER}/$errors_to/o; + $tmpl =~ s/\${EMAIL_REPLY_TO}/$errors_to/; + $tmpl =~ s/\${EMAIL_ERRORS_TO}/$errors_to/; + $tmpl =~ s/\${EMAIL_HEADERS}//; # - we have no additional headers to add $tmpl =~ s/\${RANGE}/$r/; $tmpl =~ s/\${DATE}/$mon\/$day\/$year/; @@ -491,6 +505,7 @@ sub handle_event { my $evt = shift; warn "OD_notice: ".Dumper($evt) . "\n"; $logger->error("OD_notice: ".Dumper($evt)); + return undef; } diff --git a/Evergreen/src/support-scripts/eg_gen_overdue.sh b/Evergreen/src/support-scripts/eg_gen_overdue.sh index 23a03cbbca..c0f75b87c6 100755 --- a/Evergreen/src/support-scripts/eg_gen_overdue.sh +++ b/Evergreen/src/support-scripts/eg_gen_overdue.sh @@ -12,7 +12,7 @@ SSH_CLIENT=$1 RECIPIENT=$2; DATE=$(date +%Y-%m-%d); DAY=$(date +%u); -BSCONFIG="/openils/conf/bootstrap.conf" +BSCONFIG="/openils/conf/bootstrap_od.conf" ODDIR="/openils/var/data/overdue"; export EG_OVERDUE_EMAIL_TEMPLATE="../extras/overdue_notice_email"; diff --git a/Evergreen/xul/staff_client/server/patron/ue.js b/Evergreen/xul/staff_client/server/patron/ue.js index 68e4c9e59f..9e77d696f2 100644 --- a/Evergreen/xul/staff_client/server/patron/ue.js +++ b/Evergreen/xul/staff_client/server/patron/ue.js @@ -35,6 +35,9 @@ function uEditInit() { /* ------------------------------------------------------------------------------ */ function uEditFetchIdentTypes() { _debug("uEditFetchIdentTypes()"); + var s = fetchXULStash(); + if (typeof s.list != 'undefined') + if (typeof s.list.cit != 'undefined') return s.list.cit; var req = new Request(FETCH_ID_TYPES); req.send(true); return req.result(); @@ -42,6 +45,9 @@ function uEditFetchIdentTypes() { function uEditFetchStatCats() { _debug("uEditFetchStatCats()"); + var s = fetchXULStash(); + if (typeof s.list != 'undefined') + if (typeof s.list.my_actsc != 'undefined') return s.list.my_actsc; var req = new Request(SC_FETCH_ALL, SESSION); req.send(true); return req.result(); @@ -49,6 +55,9 @@ function uEditFetchStatCats() { function uEditFetchSurveys() { _debug("uEditFetchSurveys()"); + var s = fetchXULStash(); + if (typeof s.list != 'undefined') + if (typeof s.list.asv != 'undefined') return s.list.asv; var req = new Request(SV_FETCH_ALL, SESSION); req.send(true); return req.result(); @@ -56,6 +65,9 @@ function uEditFetchSurveys() { function uEditFetchGroups() { _debug("uEditFetchGroups()"); + var s = fetchXULStash(); + if (typeof s.tree != 'undefined') + if (typeof s.tree.pgt != 'undefined') return s.tree.pgt; var req = new Request(FETCH_GROUPS); req.send(true); return req.result(); @@ -63,6 +75,9 @@ function uEditFetchGroups() { function uEditFetchNetLevels() { _debug("uEditFetchNetLevels()"); + var s = fetchXULStash(); + if (typeof s.list != 'undefined') + if (typeof s.list.cnal != 'undefined') return s.list.cnal; var req = new Request(FETCH_NET_LEVELS, SESSION); req.send(true); return req.result(); @@ -190,19 +205,27 @@ function uEditClone(clone) { var cloneUser = fetchFleshedUser(clone); patron.usrgroup(cloneUser.usrgroup()); - if( cloneUser.day_phone() ) + if( cloneUser.day_phone() ) { $('ue_day_phone').value = cloneUser.day_phone(); - if( cloneUser.evening_phone() ) + $('ue_day_phone').onchange(); + } + + if( cloneUser.evening_phone() ) { $('ue_night_phone').value = cloneUser.evening_phone(); - if( cloneUser.other_phone() ) - $('ue_other_phone').value = cloneUser.other_phone(); - setSelector($('ue_org_selector'), cloneUser.home_ou()); + $('ue_night_phone').onchange(); + } + if( cloneUser.other_phone() ) { + $('ue_other_phone').value = cloneUser.other_phone(); + $('ue_other_phone').onchange(); + } + setSelector($('ue_org_selector'), cloneUser.home_ou()); setSelector($('ue_profile'), cloneUser.profile()); /* force the expire date to be set */ $('ue_profile').onchange(); + $('ue_org_selector').onchange(); for( var a in cloneUser.addresses() ) { var addr = cloneUser.addresses()[a]; @@ -482,7 +505,13 @@ function uEditSaveUser(cloneme) { var evt; if( (evt = checkILSEvent(newuser)) || ! newuser ) { if(evt) { - var j = js2JSON(newuser); + evt = newuser; + if( evt.textcode == 'XACT_COLLISION' ) { + if( confirmId('ue_xact_collision') ) + location.href = location.href; + return; + } + var j = js2JSON(evt); alert(j); _debug("USER UPDATE FAILED:\n" + j); } diff --git a/Evergreen/xul/staff_client/server/patron/ue.xhtml b/Evergreen/xul/staff_client/server/patron/ue.xhtml index 40cff81987..9024a5c9dc 100644 --- a/Evergreen/xul/staff_client/server/patron/ue.xhtml +++ b/Evergreen/xul/staff_client/server/patron/ue.xhtml @@ -1003,6 +1003,14 @@ Are you sure you wish to perform this action? + + It appears that someone else was also editing this user. Saving the user + now will destroy their changes. Click "OK" to refresh the user and continue + editing. Click "Cancel" to do nothing. + + Note that you will not be able to save the user until this page has been refreshed. + + diff --git a/Evergreen/xul/staff_client/server/patron/ue_config.js b/Evergreen/xul/staff_client/server/patron/ue_config.js index 1d6a3cc19a..6b6449ab5b 100644 --- a/Evergreen/xul/staff_client/server/patron/ue_config.js +++ b/Evergreen/xul/staff_client/server/patron/ue_config.js @@ -760,18 +760,12 @@ function uEditBuildAddrFields(patron, address) { var state = $n(f.widget.base, 'ue_addr_state'); var county = $n(f.widget.base, 'ue_addr_county'); var city = $n(f.widget.base, 'ue_addr_city'); - if(!state.value) { - state.value = info.state; - state.onchange(); - } - if(!county.value) { - county.value = info.county; - county.onchange(); - } - if(!city.value) { - city.value = info.city; - city.onchange(); - } + state.value = info.state; + state.onchange(); + county.value = info.county; + county.onchange(); + city.value = info.city; + city.onchange(); } ); req.send(); @@ -1009,6 +1003,7 @@ function uEditCheckSharedAddr(patron, address, tbody, row) { function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) }; if( userCache[id] ) { + var usr = userCache[id]; nnode.appendChild(text( usr.first_given_name() + ' ' + usr.family_name())); diff --git a/Makefile b/Makefile index fbb6bc0e5f..2996d44e15 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ verbose: oldconfig config: @./config.sh +default_config: + @./config.sh default + oldconfig: install.conf install.conf: diff --git a/Open-ILS/admin/ils_admin/setup/models.py b/Open-ILS/admin/ils_admin/setup/models.py index f095825158..920f8e18dd 100644 --- a/Open-ILS/admin/ils_admin/setup/models.py +++ b/Open-ILS/admin/ils_admin/setup/models.py @@ -1,185 +1,267 @@ from django.db import models +from django.db.models import signals +from django.dispatch import dispatcher # Create your models here. INTERVAL_HELP_TEXT = 'examples: "1 hour", "14 days", "3 months", "DD:HH:MM:SS.ms"' +PG_SCHEMAS = "actor, permission, public, config" + + +# --------------------------------------------------------------------- +# Here we run some SQL to manually set the postgres schema search-paths +# --------------------------------------------------------------------- +def setSearchPath(): + from django.db import connection + cursor = connection.cursor() + cursor.execute("SET search_path TO %s" % PG_SCHEMAS) +dispatcher.connect(setSearchPath, signal=signals.class_prepared) +dispatcher.connect(setSearchPath, signal=signals.pre_init) + class GrpTree(models.Model): - name = models.CharField(maxlength=100) - parent_id = models.ForeignKey('self', null=True, related_name='children', db_column='parent') - description = models.CharField(blank=True, maxlength=200) - perm_interval = models.CharField(blank=True, maxlength=100, help_text=INTERVAL_HELP_TEXT) - application_perm = models.CharField(blank=True, maxlength=100) - usergroup = models.BooleanField() - class Admin: - list_display = ('name', 'description') - list_filter = ['parent_id'] - search_fields = ['name', 'description'] - class Meta: - db_table = 'grp_tree' - ordering = ['name'] - verbose_name = 'User Group' - def __str__(self): - return self.name + name = models.CharField(maxlength=100) + parent_id = models.ForeignKey('self', null=True, related_name='children', db_column='parent') + description = models.CharField(blank=True, maxlength=200) + perm_interval = models.CharField(blank=True, maxlength=100, help_text=INTERVAL_HELP_TEXT) + application_perm = models.CharField(blank=True, maxlength=100) + usergroup = models.BooleanField() + class Admin: + list_display = ('name', 'description') + list_filter = ['parent_id'] + search_fields = ['name', 'description'] + class Meta: + db_table = 'grp_tree' + ordering = ['name'] + verbose_name = 'User Group' + def __str__(self): + return self.name class OrgUnitType(models.Model): - name = models.CharField(maxlength=100) - opac_label = models.CharField(maxlength=100) - depth = models.IntegerField() - parent_id = models.ForeignKey('self', null=True, related_name='children', db_column='parent') - can_have_vols = models.BooleanField() - can_have_users = models.BooleanField() - class Meta: - db_table = 'org_unit_type' - verbose_name = 'Library Type' - class Admin: - list_display = ('name', 'depth') - list_filter = ['parent_id'] - ordering = ['depth'] - def __str__(self): - return self.name + name = models.CharField(maxlength=100) + opac_label = models.CharField(maxlength=100) + depth = models.IntegerField() + parent_id = models.ForeignKey('self', null=True, related_name='children', db_column='parent') + can_have_vols = models.BooleanField() + can_have_users = models.BooleanField() + class Meta: + db_table = 'org_unit_type' + verbose_name = 'Library Type' + class Admin: + list_display = ('name', 'depth') + list_filter = ['parent_id'] + ordering = ['depth'] + def __str__(self): + return self.name class PermList(models.Model): - code = models.CharField(maxlength=100) - description = models.CharField(blank=True, maxlength=200) - class Admin: - list_display = ('code','description') - search_fields = ['code'] - class Meta: - db_table = 'perm_list' - ordering = ['code'] - verbose_name = 'Permission' - def __str__(self): - return self.code + code = models.CharField(maxlength=100) + description = models.CharField(blank=True, maxlength=200) + class Admin: + list_display = ('code','description') + search_fields = ['code'] + class Meta: + db_table = 'perm_list' + ordering = ['code'] + verbose_name = 'Permission' + def __str__(self): + return self.code class GrpPermMap(models.Model): - grp_id = models.ForeignKey(GrpTree, db_column='grp') - perm_id = models.ForeignKey(PermList, db_column='perm') - depth_id = models.ForeignKey(OrgUnitType, to_field='depth', db_column='depth') - grantable = models.BooleanField() - class Admin: - list_filter = ['grp_id'] - search_fields = ['grp_id', 'perm_id'] - list_display = ('grp_id', 'perm_id') - class Meta: - db_table = 'grp_perm_map' - ordering = ['grp_id'] - verbose_name = 'Permission Setting' - def __str__(self): - return str(self.grp_id)+' -> '+str(self.perm_id) + grp_id = models.ForeignKey(GrpTree, db_column='grp') + perm_id = models.ForeignKey(PermList, db_column='perm') + depth_id = models.ForeignKey(OrgUnitType, to_field='depth', db_column='depth') + grantable = models.BooleanField() + class Admin: + list_filter = ['grp_id'] + list_display = ('perm_id', 'grp_id', 'depth_id') + class Meta: + db_table = 'grp_perm_map' + ordering = ['perm_id', 'grp_id'] + verbose_name = 'Permission Setting' + def __str__(self): + return str(self.grp_id)+' -> '+str(self.perm_id) + + + +""" There's no way to do user-based mangling given the size of the data without custom handling. + When you try to create a new permission map, it tries to load all users into a dropdown selector :( + +class User(models.Model): + card_id = models.ForeignKey('Card', db_column='card') + profile_id = models.ForeignKey(GrpTree, db_column='profile') + usrname = models.CharField(blank=False, null=False, maxlength=200) + def __str__(self): + return "%s (%s)" % ( str(self.card_id), str(self.usrname)) + class Meta: + db_table = 'usr' + verbose_name = 'User' + +class UsrPermMap(models.Model): + usr_id = models.ForeignKey(User, db_column='usr') + perm_id = models.ForeignKey(PermList, db_column='perm') + depth_id = models.ForeignKey(OrgUnitType, to_field='depth', db_column='depth') + grantable = models.BooleanField() + class Admin: + search_fields = ['usr_id', 'perm_id'] # we need text fields to search... + class Meta: + db_table = 'usr_perm_map' + verbose_name = 'User Permission' + def __str__(self): + return "%s -> %s" % ( str(self.usr_id), str(self.perm_id) ) + + +class Card(models.Model): + usr_id = models.ForeignKey(User, db_column='usr') + barcode = models.CharField(blank=False, null=False, maxlength=200) + active = models.BooleanField() + def __str__(self): + return self.barcode + class Meta: + db_table = 'card' + verbose_name = 'Card' +""" + + class OrgAddress(models.Model): - valid = models.BooleanField() - org_unit_id = models.ForeignKey('OrgUnit', db_column='org_unit') - address_type = models.CharField(blank=False, maxlength=200, default='MAILING') - street1 = models.CharField(blank=False, maxlength=200) - street2 = models.CharField(maxlength=200) - city = models.CharField(blank=False, maxlength=200) - county = models.CharField(maxlength=200) - state = models.CharField(blank=False, maxlength=200) - country = models.CharField(blank=False, maxlength=200) - post_code = models.CharField(blank=False, maxlength=200) - class Admin: - search_fields = ['street1', 'city', 'post_code'] - list_filter = ['org_unit_id'] - list_display = ('street1', 'street2', 'city', 'county', 'state', 'post_code') - class Meta: - ordering = ['city'] - db_table = 'org_address' - verbose_name = 'Library Address' - def __str__(self): - return self.street1+' '+self.city+', '+self.state+' '+self.post_code + valid = models.BooleanField() + org_unit_id = models.ForeignKey('OrgUnit', db_column='org_unit') + address_type = models.CharField(blank=False, maxlength=200, default='MAILING') + street1 = models.CharField(blank=False, maxlength=200) + street2 = models.CharField(maxlength=200) + city = models.CharField(blank=False, maxlength=200) + county = models.CharField(maxlength=200) + state = models.CharField(blank=False, maxlength=200) + country = models.CharField(blank=False, maxlength=200) + post_code = models.CharField(blank=False, maxlength=200) + class Admin: + search_fields = ['street1', 'city', 'post_code'] + list_filter = ['org_unit_id'] + list_display = ('street1', 'street2', 'city', 'county', 'state', 'post_code') + class Meta: + ordering = ['city'] + db_table = 'org_address' + verbose_name = 'Library Address' + def __str__(self): + return self.street1+' '+self.city+', '+self.state+' '+self.post_code class OrgUnit(models.Model): - parent_ou_id = models.ForeignKey('self', null=True, related_name='children', db_column='parent_ou') - ou_type_id = models.ForeignKey(OrgUnitType, db_column='ou_type') - shortname = models.CharField(maxlength=200) - name = models.CharField(maxlength=200) - email = models.EmailField(null=True, blank=True) - phone = models.CharField(maxlength=200, null=True, blank=True) - ill_address_id = models.ForeignKey(OrgAddress, db_column='ill_address', null=True, blank=True) - holds_address_id = models.ForeignKey(OrgAddress, db_column='holds_address', null=True, blank=True) - mailing_address_id = models.ForeignKey(OrgAddress, db_column='mailing_address', null=True, blank=True) - billing_address_id = models.ForeignKey(OrgAddress, db_column='billing_address', null=True, blank=True) - class Admin: - search_fields = ['name', 'shortname'] - list_display = ('shortname', 'name') - class Meta: - db_table = 'org_unit' - ordering = ['shortname'] - verbose_name = 'Library' - def __str__(self): - return self.shortname + parent_ou_id = models.ForeignKey('self', null=True, related_name='children', db_column='parent_ou') + ou_type_id = models.ForeignKey(OrgUnitType, db_column='ou_type') + shortname = models.CharField(maxlength=200) + name = models.CharField(maxlength=200) + email = models.EmailField(null=True, blank=True) + phone = models.CharField(maxlength=200, null=True, blank=True) + opac_visible = models.BooleanField(blank=True) + ill_address_id = models.ForeignKey(OrgAddress, db_column='ill_address', null=True, blank=True) + holds_address_id = models.ForeignKey(OrgAddress, db_column='holds_address', null=True, blank=True) + mailing_address_id = models.ForeignKey(OrgAddress, db_column='mailing_address', null=True, blank=True) + billing_address_id = models.ForeignKey(OrgAddress, db_column='billing_address', null=True, blank=True) + class Admin: + search_fields = ['name', 'shortname'] + #list_filter = ['parent_ou_id'] # works, but shows all libs as options, so ruins the point + list_display = ('shortname', 'name') + class Meta: + db_table = 'org_unit' + ordering = ['shortname'] + verbose_name = 'Library' + def __str__(self): + return self.shortname class RuleCircDuration(models.Model): - name = models.CharField(maxlength=200) - extended = models.CharField(maxlength=200, help_text=INTERVAL_HELP_TEXT); - normal = models.CharField(maxlength=200, help_text=INTERVAL_HELP_TEXT); - shrt = models.CharField(maxlength=200, help_text=INTERVAL_HELP_TEXT); - max_renewals = models.IntegerField() - class Admin: - search_fields = ['name'] - list_display = ('name','extended','normal','shrt','max_renewals') - class Meta: - db_table = 'rule_circ_duration' - ordering = ['name'] - verbose_name = 'Circ Duration Rule' - def __str__(self): - return self.name + name = models.CharField(maxlength=200) + extended = models.CharField(maxlength=200, help_text=INTERVAL_HELP_TEXT); + normal = models.CharField(maxlength=200, help_text=INTERVAL_HELP_TEXT); + shrt = models.CharField(maxlength=200, help_text=INTERVAL_HELP_TEXT); + max_renewals = models.IntegerField() + class Admin: + search_fields = ['name'] + list_display = ('name','extended','normal','shrt','max_renewals') + class Meta: + db_table = 'rule_circ_duration' + ordering = ['name'] + verbose_name = 'Circ Duration Rule' + def __str__(self): + return self.name class RuleMaxFine(models.Model): - name = models.CharField(maxlength=200) - amount = models.FloatField(max_digits=6, decimal_places=2) - class Admin: - search_fields = ['name'] - list_display = ('name','amount') - class Meta: - db_table = 'rule_max_fine' - ordering = ['name'] - verbose_name = 'Circ Max Fine Rule' - def __str__(self): - return self.name + name = models.CharField(maxlength=200) + amount = models.FloatField(max_digits=6, decimal_places=2) + class Admin: + search_fields = ['name'] + list_display = ('name','amount') + class Meta: + db_table = 'rule_max_fine' + ordering = ['name'] + verbose_name = 'Circ Max Fine Rule' + def __str__(self): + return self.name class RuleRecurringFine(models.Model): - name = models.CharField(maxlength=200) - high = models.FloatField(max_digits=6, decimal_places=2) - normal = models.FloatField(max_digits=6, decimal_places=2) - low = models.FloatField(max_digits=6, decimal_places=2) - class Admin: - search_fields = ['name'] - list_display = ('name','high', 'normal', 'low') - class Meta: - db_table = 'rule_recuring_fine' - ordering = ['name'] - verbose_name = 'Circ Recurring Fine Rule' - def __str__(self): - return self.name + name = models.CharField(maxlength=200) + high = models.FloatField(max_digits=6, decimal_places=2) + normal = models.FloatField(max_digits=6, decimal_places=2) + low = models.FloatField(max_digits=6, decimal_places=2) + class Admin: + search_fields = ['name'] + list_display = ('name','high', 'normal', 'low') + class Meta: + db_table = 'rule_recuring_fine' + ordering = ['name'] + verbose_name = 'Circ Recurring Fine Rule' + def __str__(self): + return self.name class IdentificationType(models.Model): - name = models.CharField(maxlength=200) - class Admin: - search_fields = ['name'] - class Meta: - db_table = 'identification_type' - ordering = ['name'] - verbose_name = 'Identification Type' - def __str__(self): - return self.name + name = models.CharField(maxlength=200) + class Admin: + search_fields = ['name'] + class Meta: + db_table = 'identification_type' + ordering = ['name'] + verbose_name = 'Identification Type' + def __str__(self): + return self.name class RuleAgeHoldProtect(models.Model): - name = models.CharField(maxlength=200) - age = models.CharField(blank=True, maxlength=100, help_text=INTERVAL_HELP_TEXT) - prox = models.IntegerField() - class Admin: - search_fields = ['name'] - class Meta: - db_table = 'rule_age_hold_protect' - ordering = ['name'] - verbose_name = 'Hold Age Protection Rule' - def __str__(self): - return self.name + name = models.CharField(maxlength=200) + age = models.CharField(blank=True, maxlength=100, help_text=INTERVAL_HELP_TEXT) + prox = models.IntegerField() + class Admin: + search_fields = ['name'] + class Meta: + db_table = 'rule_age_hold_protect' + ordering = ['name'] + verbose_name = 'Hold Age Protection Rule' + def __str__(self): + return self.name + +class MetabibField(models.Model): + field_class_choices = ( + ('title', 'Title'), + ('author', 'Author'), + ('subject', 'Subject'), + ('series', 'Series'), + ('keyword', 'Keyword'), + ) + field_class = models.CharField(maxlength=200, choices=field_class_choices, null=False, blank=False) + name = models.CharField(maxlength=200, null=False, blank=False) + xpath = models.TextField(null=False, blank=False) + weight = models.IntegerField(null=False, blank=False) + format = models.CharField(maxlength=200, null=False, blank=False) + class Admin: + search_fields = ['name', 'format', 'field_class'] + list_display = ('field_class', 'name', 'format') + class Meta: + db_table = 'metabib_field' + ordering = ['field_class', 'name'] + verbose_name = 'Metabib Field' + def __str__(self): + return self.name + diff --git a/Open-ILS/examples/apache/eg.conf b/Open-ILS/examples/apache/eg.conf new file mode 100644 index 0000000000..79afa6b332 --- /dev/null +++ b/Open-ILS/examples/apache/eg.conf @@ -0,0 +1,139 @@ +# :vim set syntax apache + +LogLevel debug +# - log locally +CustomLog /var/log/apache2/access.log combined +ErrorLog /var/log/apache2/error.log +# - log to syslog +# CustomLog "|/usr/bin/logger -p local7.info" common +# ErrorLog syslog:local7 + + +# ---------------------------------------------------------------------------------- +# Set up Perl +# ---------------------------------------------------------------------------------- + +# - needed by CGIs +SetEnv PERL5LIB /openils/lib/perl5 +PerlRequire /etc/apache2/startup.pl +PerlChildInitHandler OpenILS::WWW::Reporter::child_init +PerlChildInitHandler OpenILS::WWW::SuperCat::child_init +PerlChildInitHandler OpenILS::WWW::AddedContent::child_init; + + +# ---------------------------------------------------------------------------------- +# Set some defaults for our working directories +# ---------------------------------------------------------------------------------- + + Order allow,deny + Allow from all + + + +# ---------------------------------------------------------------------------------- +# XUL directory +# ---------------------------------------------------------------------------------- + + Options Indexes FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + + + +# ---------------------------------------------------------------------------------- +# Remove the language portion from the URL +# ---------------------------------------------------------------------------------- +AliasMatch ^/opac/.*/skin/(.*)/(.*)/(.*) /openils/var/web/opac/skin/$1/$2/$3 + + +# ---------------------------------------------------------------------------------- +# System config CGI scripts go here +# ---------------------------------------------------------------------------------- +Alias /cgi-bin/ "/openils/var/cgi-bin/" + + AddHandler cgi-script .cgi .pl + AllowOverride None + Options None + Order deny,allow + Deny from all + Allow from 10.0.0.0/8 + Options FollowSymLinks ExecCGI Indexes + + + + +# ---------------------------------------------------------------------------------- +# OPTIONAL: Set up image caching - some of these options only work with apache2.2 +# ---------------------------------------------------------------------------------- +CacheRoot "/opt/cache/" +CacheEnable disk /opac/extras/jacket/ +CacheMaxFileSize 1073741824 +CacheIgnoreCacheControl On +CacheStorePrivate On +CacheStoreNoStore On +CacheIgnoreNoLastMod On +CacheMaxExpire 86400 +CacheLastModifiedFactor 0.5 +CacheDefaultExpire 604800 + + +# ---------------------------------------------------------------------------------- +# OPTIONAL: Set how long the client will cache our content. Change to suit +# ---------------------------------------------------------------------------------- +ExpiresActive On +ExpiresDefault A2592000 +ExpiresByType text/html A64800 +ExpiresByType application/xhtml+xml A64800 +ExpiresByType application/x-javascript A64800 +ExpiresByType text/css A3000 + + + + +# ---------------------------------------------------------------------------------- +# Set up our main virtual host +# ---------------------------------------------------------------------------------- +NameVirtualHost *:80 + + ServerName localhost:80 + ServerAlias 127.0.0.1:80 + DocumentRoot /openils/var/web/ + DirectoryIndex index.xml index.html + # - absorb the shared virtual host settings + Include eg_vhost.conf + + + + + + +# ---------------------------------------------------------------------------------- +# Set up our SSL virtual host +# ---------------------------------------------------------------------------------- +Listen 443 +NameVirtualHost *:443 + + DocumentRoot "/openils/var/web" + ServerName localhost:443 + ServerAlias 127.0.0.1:443 + SSLEngine on + SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL + + # If you don't have an SSL cert, you can create self-signed + # certificate and key with: + # openssl req -new -x509 -nodes -out server.crt -keyout server.key + SSLCertificateFile ssl/server.crt + SSLCertificateKeyFile ssl/server.key + + # - absorb the shared virtual host settings + Include eg_vhost.conf + + # help IE along with SSL pages + BrowserMatch ".*MSIE.*" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + + + + diff --git a/Open-ILS/examples/apache/eg_vhost.conf b/Open-ILS/examples/apache/eg_vhost.conf new file mode 100644 index 0000000000..105a0c06bc --- /dev/null +++ b/Open-ILS/examples/apache/eg_vhost.conf @@ -0,0 +1,217 @@ +# ---------------------------------------------------------------------------------- +# This is the global Evergreen virtual host config. Anything you want published +# through all virtual hosts (port 80, port 443, etc.) should live in here. +# ---------------------------------------------------------------------------------- + + +# ---------------------------------------------------------------------------------- +# Point / to the opac +# ---------------------------------------------------------------------------------- +RedirectMatch 301 ^/$ /opac/en-US/skin/default/xml/index.xml + + +# ---------------------------------------------------------------------------------- +# Configure the gateway +# ---------------------------------------------------------------------------------- +OSRFGatewayConfig /openils/conf/opensrf_core.xml + + +# ---------------------------------------------------------------------------------- +# Set up the book jackets URL +# XXX This pulls images from Amazon, don't use this in a production environment +# ---------------------------------------------------------------------------------- +RewriteEngine on +ProxyTimeout 2 +RewriteRule /opac/extras/jacket/small/(.*) \ + http://images.amazon.com/images/P/$1.01._SCMZZZZZZZ_.jpg [P,L] +RewriteRule /opac/extras/jacket/large/(.*) \ + http://images.amazon.com/images/P/$1.01._SCLZZZZZZZ_.jpg [P,L] + + + +# ---------------------------------------------------------------------------------- +# Added content plugin +# ---------------------------------------------------------------------------------- + + SetHandler perl-script + PerlHandler OpenILS::WWW::AddedContent + Options +ExecCGI + PerlSendHeader On + allow from all + + + +# ---------------------------------------------------------------------------------- +# Configure the OPAC +# ---------------------------------------------------------------------------------- + + AddType application/xhtml+xml .xml + + # - configure mod_xmlent + XMLEntStripPI "yes" + XMLEntEscapeScript "no" + XMLEntStripComments "yes" + XMLEntContentType "text/html; charset=utf-8" + # forces quirks mode which we want for now + XMLEntStripDoctype "yes" + + # - set up the include handlers + Options +Includes + AddOutputFilter INCLUDES .xsl + AddOutputFilter INCLUDES;XMLENT .xml + + # add languages as necessary + SetEnvIf Request_URI "/en-US/" locale=en-US + SetEnvIf Request_URI "/fr/" locale=fr + SetEnvIf Request_URI ".*" OILS_OPAC_BASE=/opac/ + + + +# ---------------------------------------------------------------------------------- +# Force SSL on the OPAC's "My Account" page +# ---------------------------------------------------------------------------------- + + SSLRequireSSL + + + + AddType application/xhtml+xml .xml + + + + AddOutputFilter INCLUDES .html + + +# ---------------------------------------------------------------------------------- +# Run server-side XUL through xmlent to load the correct XML entities +# ---------------------------------------------------------------------------------- + + Options +Includes + XMLEntContentType "application/vnd.mozilla.xul+xml" + AddOutputFilter INCLUDES;XMLENT .xul + allow from all + + +# ---------------------------------------------------------------------------------- +# Supercat feeds +# ---------------------------------------------------------------------------------- + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::oisbn + Options +ExecCGI + PerlSendHeader On + allow from all + + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::supercat + Options +ExecCGI + PerlSendHeader On + allow from all + + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::unapi + Options +ExecCGI + PerlSendHeader On + allow from all + + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::bookbag_feed + Options +ExecCGI + PerlSendHeader On + allow from all + + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::opensearch_feed + Options +ExecCGI + PerlSendHeader On + allow from all + + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::changes_feed + Options +ExecCGI + PerlSendHeader On + allow from all + + + SetHandler perl-script + PerlHandler OpenILS::WWW::SuperCat::string_browse + Options +ExecCGI + PerlSendHeader On + allow from all + + +# ---------------------------------------------------------------------------------- +# Module for processing staff-client offline scripts lives here +# ---------------------------------------------------------------------------------- + + AddHandler cgi-script .pl + AllowOverride None + Options +ExecCGI + allow from all + + + +# ---------------------------------------------------------------------------------- +# OpenSRF JSON gateway +# ---------------------------------------------------------------------------------- + + SetHandler osrf_json_gateway_module + allow from all + + + +# ---------------------------------------------------------------------------------- +# Reporting output lives here +# ---------------------------------------------------------------------------------- + + SetHandler perl-script + PerlHandler OpenILS::Reporter::Proxy + Options +ExecCGI + PerlSendHeader On + allow from all + + +# ---------------------------------------------------------------------------------- +# Reports GUI +# ---------------------------------------------------------------------------------- + + Options +Includes + AddOutputFilter INCLUDES .xhtml + + +# ---------------------------------------------------------------------------------- +# XML-RPC gateway +# ---------------------------------------------------------------------------------- + + SetHandler perl-script + PerlHandler OpenILS::WWW::XMLRPCGateway + Options +ExecCGI + PerlSendHeader On + allow from all + + + +# ---------------------------------------------------------------------------------- +# Django admin interface (experimental) +# - requires mod_python and django +# - requires a symlink from WEBROOT/media to +# /usr/lib/python2.4/site-packages/django/contrib/admin/media/ (or similar) +# ---------------------------------------------------------------------------------- +# +# Order deny,allow +# Deny from all +# Allow from 10.0.0.0/8 +# SetHandler mod_python +# PythonHandler django.core.handlers.modpython +# SetEnv DJANGO_SETTINGS_MODULE ils_admin.settings +# PythonDebug On +# PythonPath "['/openils/var/admin/', '/usr/lib/python2.4/site-packages/'] +sys.path" +# PythonAutoReload On +# + + diff --git a/Open-ILS/examples/apache/startup.pl b/Open-ILS/examples/apache/startup.pl new file mode 100644 index 0000000000..9296f31273 --- /dev/null +++ b/Open-ILS/examples/apache/startup.pl @@ -0,0 +1,10 @@ +#!/usr/bin/perl +use lib qw( /openils/lib/perl5 ); +use OpenILS::WWW::SuperCat qw( /openils/conf/bootstrap.conf ); +use OpenILS::WWW::AddedContent qw( /openils/conf/bootstrap.conf ); +use OpenILS::Reporter::Proxy ('/openils/conf/bootstrap.conf'); + + + +1; + diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 2cfd939fae..a1f76f5054 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -1002,6 +1002,8 @@ + + @@ -1017,6 +1019,8 @@ + + @@ -1189,7 +1193,7 @@ - + @@ -1203,17 +1207,19 @@ - + - - + + - + + + @@ -1222,11 +1228,12 @@ - - - - - + + + + + + @@ -1246,6 +1253,7 @@ + @@ -1481,6 +1489,8 @@ + + @@ -1488,6 +1498,8 @@ + + @@ -1514,7 +1526,9 @@ - + + + @@ -1522,6 +1536,8 @@ + + @@ -1928,24 +1944,24 @@ - + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -1957,23 +1973,73 @@ - + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2295,4 +2361,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/examples/oils_ctl.sh b/Open-ILS/examples/oils_ctl.sh index 78c09d9683..106caf2656 100755 --- a/Open-ILS/examples/oils_ctl.sh +++ b/Open-ILS/examples/oils_ctl.sh @@ -89,10 +89,9 @@ function start_sip { do_action "start" $PID_SIP "OILS SIP Server"; DIR=$(pwd); cd $SIP_DIR; - nohup perl SIPServer.pm "$OPT_SIP_CONFIG" & + perl SIPServer.pm "$OPT_SIP_CONFIG" > /dev/null 2>&1 & pid=$!; cd $DIR; - ps ax | grep "$pid"; echo $pid > $PID_SIP; return 0; } diff --git a/Open-ILS/examples/openils.xml.example b/Open-ILS/examples/openils.xml.example index c61eb031f4..d21885d7eb 100644 --- a/Open-ILS/examples/openils.xml.example +++ b/Open-ILS/examples/openils.xml.example @@ -2,452 +2,652 @@ - - - - - - - /openils/var/log - /openils/var/sock - /openils/var/pid - - - /openils/var/xsl - /openils/var/conf - - /openils/var/data - - - prefork - - - /openils/var/data/ils_events.xml - - - oclc - - - - - zcat.oclc.org - 210 - OLUCWorldCat - - - - - 12 - 6 - - - 7 - 6 - - - 9 - 3 - - - 1003 - 6 - - - <code>4</code> - <format>6</format> - - - 8 - 1 - - - 1018 - 6 - - - 31 - 5 - - - 1001 - 1 - - - - - - - loc - z3950.loc.gov - 7090 - Voyager - - - 7 - 1 - - - - - - - - - - - - - 127.0.0.1:10101 - - 86400 - - - - - - - 1 - 1 - perl - OpenSRF::Application::Persist - 97 - - opensrf.persist_unix.sock - opensrf.persist_unix.pid - 1000 - opensrf.persist_unix.log - 5 - 25 - 2 - 5 - - - - /openils/var/db/persist.db - - - - - - - 5 - 1 - C - oils_auth.so - 93 - - - open-ils.auth_unix.sock - open-ils.auth_unix.pid - 1000 - open-ils.auth_unix.log - 5 - 35 - 2 - 5 - - - - - 0 - - - - 300 - 28800 - 300 - - - - - - - - - - 5 - 1 - perl - OpenILS::Application::Search - 93 - - - open-ils.search_unix.sock - open-ils.search_unix.pid - 1000 - open-ils.search_unix.log - 5 - 35 - 2 - 5 - - - - 127.0.0.1:10101 - 3600 - oilsMARC21slim2HTML.xsl - - - - - /openils/var/data/zips.txt - /openils/var/data/oils_authority.dict - 600 - - - - - - - - - 5 - 1 - perl - OpenILS::Application::Actor - 93 - - - open-ils.actor_unix.sock - open-ils.actor_unix.pid - 1000 - open-ils.actor_unix.log - 5 - 35 - 2 - 5 - - - - - - - - - 5 - 1 - perl - OpenILS::Application::Cat - 199 - - - open-ils.cat_unix.sock - open-ils.cat_unix.pid - 1000 - open-ils.cat_unix.log - 5 - 25 - 2 - 5 - - - - - /path/to/templates/marc/book.xml - - - - - - - - - 3 - 1 - C - osrf_math.so - 97 - - opensrf.math_unix.sock - opensrf.math_unix.pid - 1000 - opensrf.math_unix.log - 5 - 15 - 2 - 5 - - - - - 3 - 1 - C - osrf_dbmath.so - 99 - - 1000 - opensrf.dbmath_unix.log - opensrf.dbmath_unix.sock - opensrf.dbmath_unix.pid - 5 - 15 - 2 5 - - - - - - - 3 - 1 - perl - OpenILS::Application::Penalty - 99 - - 1000 - open-ils.penalty_unix.log - open-ils.penalty_unix.sock - open-ils.penalty_unix.pid - 5 - 25 - 2 - 5 - - - patron_penalty.js - /openils/var/penalty/ - - - - - - - - 3 - 1 - perl - OpenILS::Application::Circ - 99 - - 1000 - open-ils.circ_unix.log - open-ils.circ_unix.sock - open-ils.circ_unix.pid - 5 - 25 - 2 5 - - - - - permit_circ.rules - calculate_duration.rules - calculate_recurring_fines.rules - calculate_max_fines.rules - permit_hold.rules - permit_renew.rules - - - /openils/var/circ/ - - circ_permit_patron.js - circ_permit_copy.js - circ_duration.js - circ_recurring_fines.js - circ_max_fines.js - circ_permit_renew.js - circ_permit_hold.js - - - - - - - - - 3 - 1 - perl - OpenILS::Application::Storage - - 1000 - storage_unix.log - storage_unix.sock - storage_unix.pid - 1000 - 10 - 50 - 2 - 5 - - - - - Pg - - master - 2 - postgres - 127.0.0.1 - postgres - demo-dev - SQL_ASCII - - - - - - - - 1 - 1 - perl - OpenSRF::Application::Settings - 17 - - opensrf.settings_unix.sock - opoensrf.settings_unix.pid - 1000 - opensrf.settings_unix.log - 5 - 15 - 3 - 5 - - - - - - - - - - - - - - - - opensrf.math - opensrf.dbmath - opensrf.settings - - - open-ils.cat - open-ils.search - open-ils.circ - open-ils.penalty - open-ils.actor - open-ils.auth - open-ils.storage - - - - - + + + + + + /openils/var/log + /openils/var/sock + /openils/var/pid + /openils/var/xsl + + /openils/var + + + /openils/conf/fm_IDL.xml + prefork + /openils/var/data/ils_events.xml + + + + + localhost + + + evergreen@localhost + + + + + + + https://localhost/reporter/ + + Pg + localhost + 5432 + evergreen + postgres + postgres + + + + /openils/var/reporter/output + /openils/var/data/report-success + /openils/var/data/report-fail + + + + + + + + + + + opensrf.math + opensrf.dbmath + open-ils.cat + open-ils.search + open-ils.circ + open-ils.actor + open-ils.auth + open-ils.collections + + + + + + oclc + + + + zcat.oclc.org + 210 + OLUCWorldCat + + + 121 + 76 + 91 + 10036 + <code>4</code><format>6</format> + 81 + 10186 + 311 + 10011 + + + + + + + + + + + + OpenILS::WWW::AddedContent::Syndetic + MY_USER_ID + http://syndetics.com/index.aspx + 2 + + + + + + + + + + + + localhost:11211 + + 86400 + + + + + + + + + 5 + + + 1 + + + c + + + oils_auth.so + + + 93 + + + + + + + open-ils.auth_unix.sock + + + open-ils.auth_unix.pid + + + 1000 + + + open-ils.auth_unix.log + + 1 + + 15 + + 1 + + 5 + + + + + + + 420 + 7200 + 300 + + + + + + + + 5 + 1 + perl + OpenILS::Application::Search + 93 + + open-ils.search_unix.sock + open-ils.search_unix.pid + 1000 + open-ils.search_unix.log + 1 + 15 + 1 + 5 + + + oilsMARC21slim2HTML.xsl + + + + + + + + 5 + 1 + perl + OpenILS::Application::Actor + 93 + + open-ils.actor_unix.sock + open-ils.actor_unix.pid + 1000 + open-ils.actor_unix.log + 1 + 15 + 1 + 5 + + + + + + 5 + 1 + perl + OpenILS::Application::Cat + 199 + + open-ils.cat_unix.sock + open-ils.cat_unix.pid + 1000 + open-ils.cat_unix.log + 1 + 15 + 1 + 5 + + + + /openils/var/templates/marc/k_book.xml + + + + + + 5 + 1 + perl + OpenILS::Application::SuperCat + 199 + + open-ils.supercat_unix.sock + open-ils.supercat_unix.pid + 1000 + open-ils.supercat_unix.log + 1 + 15 + 1 + 5 + + + + + + 3 + 1 + c + osrf_math.so + 97 + + opensrf.math_unix.sock + opensrf.math_unix.pid + 1000 + opensrf.math_unix.log + 1 + 15 + 1 + 5 + + + + + 3 + 1 + c + osrf_dbmath.so + 99 + + 1000 + opensrf.dbmath_unix.log + opensrf.dbmath_unix.sock + opensrf.dbmath_unix.pid + 1 + 15 + 1 + 5 + + + + + 3 + 1 + perl + OpenILS::Application::Penalty + 99 + + 1000 + open-ils.penalty_unix.log + open-ils.penalty_unix.sock + open-ils.penalty_unix.pid + 1 + 15 + 1 + 5 + + + penalty/patron_penalty.js + /openils/var + /openils/var/catalog + + + + + 3 + 1 + perl + OpenILS::Application::Circ + 99 + + 1000 + open-ils.circ_unix.log + open-ils.circ_unix.sock + open-ils.circ_unix.pid + 1 + 15 + 1 + 5 + + + + false + + + + /openils/var + /openils/var/catalog + + circ/circ_permit_patron.js + circ/circ_permit_copy.js + circ/circ_duration.js + circ/circ_recurring_fines.js + circ/circ_max_fines.js + circ/circ_permit_renew.js + circ/circ_permit_hold.js + + + + art + atlas + audiobook + av + new-av + bestseller + bestsellernh + book + cd + dvd + dvd-long + e-book + equipment + filmstrip + kit + magazine + map + microform + music + record + software + softwrlong + equip-long + talking book + toy + video + video-long + + + + Miscellaneous + Overdue materials + Fee for placing a hold + Fee for checking out a book + Fee for library card + Miscellaneous charges + Lost materials + Damaged material + Overdue Reserves charge + Recall overdue + Fee for processing lost library materials + Fee for sending patron bills to collection agency + Fee for interlibrary loan + Fee for copies + Money advanced to pay for telephone use + Deposit fee + Fee for disk + Fee for faxing + Fee for laminating + Fee for room cleaning + Deposit returned; fee refund + Sale items + Fee for lost card + Long overdue items + Lost/Replacement Cassette + Returned Check + + + + + + 3 + 1 + OpenILS::Application::Ingest + perl + 1000000 + + 1000000 + open-ils.ingest-unix.log + open-ils.ingest-unix.sock + open-ils.ingest-unix.pid + 5 + 20 + 2 + 5 + + + /openils/var/catalog/ + /openils/var/web/opac/common/js/ + + biblio_fingerprint.js + biblio_descriptor.js + + + + + + 10 + 1 + perl + OpenILS::Application::Storage + + 1000 + storage_unix.log + storage_unix.sock + storage_unix.pid + 400 + 1 + 10 + 1 + 5 + + + /openils/var/catalog/ + + biblio_fingerprint.js + + + Pg + + master + 2 + postgres + localhost + 5432 + postgres + evergreen + UTF-8 + + + + + + + + 6 + 1 + C + oils_cstore.so + 95 + + cstore.log + cstore.sock + cstore.pid + 400 + 1 + 15 + 1 + 5 + + + /openils/conf/fm_IDL.xml + pgsql + + master + 2 + postgres + localhost + 5432 + postgres + evergreen + UTF-8 + + + + + + + + 1 + 1 + perl + OpenSRF::Application::Settings + 17 + + opensrf.settings_unix.sock + opensrf.settings_unix.pid + 300 + opensrf.settings_unix.log + 5 + 15 + 3 + 5 + + + + + 3 + 1 + perl + OpenILS::Application::Collections + 17 + + opensrf.collections_unix.sock + opensrf.collections_unix.pid + 1000 + opensrf.collections_unix.log + 1 + 10 + 1 + 5 + + + + + 3 + 1 + perl + OpenILS::Application::Reporter + 99 + + opensrf.reporter_unix.sock + opensrf.reporter_unix.pid + 1000 + opensrf.reporter_unix.log + 1 + 10 + 1 + 5 + + + + + + 6 + 1 + C + oils_rstore.so + 95 + + rstore.log + rstore.sock + rstore.pid + 400 + 1 + 10 + 1 + 5 + + + /openils/conf/fm_IDL.xml + pgsql + + master + 2 + postgres + localhost + 5432 + postgres + evergreen + UTF-8 + + + + + + + + + + + + + opensrf.settings + opensrf.math + opensrf.dbmath + open-ils.cat + open-ils.supercat + open-ils.search + open-ils.circ + open-ils.actor + open-ils.auth + open-ils.storage + open-ils.penalty + open-ils.cstore + open-ils.collections + open-ils.ingest + open-ils.reporter + open-ils.reporter-store + + + diff --git a/Open-ILS/src/Makefile b/Open-ILS/src/Makefile index 8e8a2e7f39..1103c5f47f 100644 --- a/Open-ILS/src/Makefile +++ b/Open-ILS/src/Makefile @@ -1,7 +1,7 @@ export TMPDIR = $(TMP)/opensrf -export LDFLAGS = -L $(TMPDIR) -L . -export CFLAGS = -pipe -g -Wall -O2 -fPIC -I$(LIBXML2_HEADERS) -I$(APACHE2_HEADERS) -I$(APR_HEADERS) \ +export LDFLAGS += -L $(TMPDIR) -L . +export CFLAGS += -pipe -g -Wall -O2 -fPIC -I$(LIBXML2_HEADERS) -I$(APACHE2_HEADERS) -I$(APR_HEADERS) \ -I$(LIBXML2_HEADERS)/libxml -I$(TMP) -I$(TMPDIR) export INCDIR = "$(INCLUDEDIR)/openils/" @@ -62,6 +62,8 @@ server-xul: webcore-install: @echo $@ echo "Copying web into $(WEBDIR)" + mkdir -p $(ADMINDIR) + cp -r ../admin/* $(ADMINDIR) mkdir -p $(WEBDIR) mkdir -p $(WEBDIR)/opac/extras/xsl/ mkdir -p $(WEBDIR)/opac/extras/slimpac/ @@ -134,12 +136,12 @@ perl-install: reporter-install: @echo $@ - @echo "Installing Reporter templates to $(REPORTERDIR) and example configs to $(ETCDIR)" - cp reporter/report_base.example.xml $(ETCDIR)/reporter.example.xml - cp reporter/tables.example.xml $(ETCDIR) - cp reporter/widgets.example.xml $(ETCDIR) - mkdir -p $(REPORTERDIR) - cp -r reporter/templates/* $(REPORTERDIR) +# @echo "Installing Reporter templates to $(REPORTERDIR) and example configs to $(ETCDIR)" +# cp reporter/report_base.example.xml $(ETCDIR)/reporter.example.xml +# cp reporter/tables.example.xml $(ETCDIR) +# cp reporter/widgets.example.xml $(ETCDIR) +# mkdir -p $(REPORTERDIR) +# cp -r reporter/templates/* $(REPORTERDIR) # ----------------------------------------------------------------------------------- offline-install: diff --git a/Open-ILS/src/c-apps/oils_cstore.c b/Open-ILS/src/c-apps/oils_cstore.c index 60c08dd221..e41cf4950f 100644 --- a/Open-ILS/src/c-apps/oils_cstore.c +++ b/Open-ILS/src/c-apps/oils_cstore.c @@ -23,8 +23,11 @@ #define OBJECT_NS "http://open-ils.org/spec/opensrf/IDL/objects/v1" #define BASE_NS "http://opensrf.org/spec/IDL/base/v1" +#define SELECT_DISTINCT 1 + int osrfAppChildInit(); int osrfAppInitialize(); +void osrfAppChildExit(); int verifyObjectClass ( osrfMethodContext*, jsonObject* ); @@ -36,24 +39,31 @@ int setSavepoint ( osrfMethodContext* ); int releaseSavepoint ( osrfMethodContext* ); int rollbackSavepoint ( osrfMethodContext* ); +int doJSONSearch ( osrfMethodContext* ); + int dispatchCRUDMethod ( osrfMethodContext* ); jsonObject* doCreate ( osrfMethodContext*, int* ); jsonObject* doRetrieve ( osrfMethodContext*, int* ); jsonObject* doUpdate ( osrfMethodContext*, int* ); jsonObject* doDelete ( osrfMethodContext*, int* ); -jsonObject* doSearch ( osrfMethodContext*, osrfHash*, jsonObject*, int* ); -jsonObject* oilsMakeJSONFromResult( dbi_result, osrfHash* ); - -char* searchWriteSimplePredicate ( osrfHash*, const char*, const char*, const char* ); -char* searchSimplePredicate ( const char*, osrfHash*, jsonObject* ); -char* searchFunctionPredicate ( osrfHash*, jsonObjectNode* ); -char* searchFieldTransform (osrfHash*, jsonObject*); -char* searchFieldTransformPredicate ( osrfHash*, jsonObjectNode* ); -char* searchBETWEENPredicate ( osrfHash*, jsonObject* ); -char* searchINPredicate ( osrfHash*, jsonObject*, const char* ); -char* searchPredicate ( osrfHash*, jsonObject* ); +jsonObject* doFieldmapperSearch ( osrfMethodContext*, osrfHash*, jsonObject*, int* ); +jsonObject* oilsMakeFieldmapperFromResult( dbi_result, osrfHash* ); +jsonObject* oilsMakeJSONFromResult( dbi_result ); + +char* searchWriteSimplePredicate ( const char*, osrfHash*, const char*, const char*, const char* ); +char* searchSimplePredicate ( const char*, const char*, osrfHash*, jsonObject* ); +char* searchFunctionPredicate ( const char*, osrfHash*, jsonObjectNode* ); +char* searchFieldTransform (const char*, osrfHash*, jsonObject*); +char* searchFieldTransformPredicate ( const char*, osrfHash*, jsonObjectNode* ); +char* searchBETWEENPredicate ( const char*, osrfHash*, jsonObject* ); +char* searchINPredicate ( const char*, osrfHash*, jsonObject*, const char* ); +char* searchPredicate ( const char*, osrfHash*, jsonObject* ); +char* searchJOIN ( jsonObject*, osrfHash* ); +char* searchWHERE ( jsonObject*, osrfHash*, int ); char* buildSELECT ( jsonObject*, jsonObject*, osrfHash*, osrfMethodContext* ); +char* SELECT ( osrfMethodContext*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, jsonObject*, int ); + void userDataFree( void* ); void sessionDataFree( char*, void* ); @@ -62,9 +72,23 @@ dbi_conn dbhandle; /* our CURRENT db connection */ osrfHash readHandles; jsonObject* jsonNULL = NULL; // +/* called when this process is about to exit */ +void osrfAppChildExit() { + osrfLogDebug(OSRF_LOG_MARK, "Child is exiting, disconnecting from database..."); + + if (writehandle) { + dbi_conn_query(writehandle, "ROLLBACK;"); + dbi_conn_close(writehandle); + writehandle = NULL; + } + + if (dbhandle) + dbi_conn_close(dbhandle); -/* parse and store the IDL here */ -osrfHash* idl; + // XXX add cleanup of readHandles whenever that gets used + + return; +} int osrfAppInitialize() { growing_buffer* method_name; @@ -76,13 +100,16 @@ int osrfAppInitialize() { osrfLogInfo(OSRF_LOG_MARK, "Found file:"); osrfLogInfo(OSRF_LOG_MARK, idl_filename); - idl = oilsIDLInit( idl_filename ); - - if (!idl) { + if (!oilsIDLInit( idl_filename )) { osrfLogError(OSRF_LOG_MARK, "Problem loading the IDL. Seacrest out!"); exit(1); } + // Generic search thingy + method_name = buffer_init(64); + buffer_fadd(method_name, "%s.json_query", MODULENAME); + osrfAppRegisterMethod( MODULENAME, buffer_data(method_name), "doJSONSearch", "", 1, OSRF_METHOD_STREAMING ); + // first we register all the transaction and savepoint methods method_name = buffer_init(64); buffer_fadd(method_name, "%s.transaction.begin", MODULENAME); @@ -120,14 +147,14 @@ int osrfAppInitialize() { int c_index = 0; char* classname; - osrfStringArray* classes = osrfHashKeys( idl ); + osrfStringArray* classes = osrfHashKeys( oilsIDL() ); osrfLogDebug(OSRF_LOG_MARK, "%d classes loaded", classes->size ); osrfLogDebug(OSRF_LOG_MARK, "At least %d methods will be generated", classes->size * global_methods->size); while ( (classname = osrfStringArrayGetString(classes, c_index++)) ) { osrfLogInfo(OSRF_LOG_MARK, "Generating class methods for %s", classname); - osrfHash* idlClass = osrfHashGet(idl, classname); + osrfHash* idlClass = osrfHashGet(oilsIDL(), classname); if (!osrfStringArrayContains( osrfHashGet(idlClass, "controller"), MODULENAME )) { osrfLogInfo(OSRF_LOG_MARK, "%s is not listed as a controller for %s, moving on", MODULENAME, classname); @@ -246,10 +273,10 @@ int osrfAppChildInit() { unsigned short type; int i = 0; char* classname; - osrfStringArray* classes = osrfHashKeys( idl ); + osrfStringArray* classes = osrfHashKeys( oilsIDL() ); while ( (classname = osrfStringArrayGetString(classes, i++)) ) { - osrfHash* class = osrfHashGet( idl, classname ); + osrfHash* class = osrfHashGet( oilsIDL(), classname ); osrfHash* fields = osrfHashGet( class, "fields" ); char* virt = osrfHashGet(class, "virtual"); @@ -559,7 +586,7 @@ int dispatchCRUDMethod ( osrfMethodContext* ctx ) { if (!strcmp( (char*)osrfHashGet(meta, "methodtype"), "search")) { - obj = doSearch(ctx, class_obj, ctx->params, &err); + obj = doFieldmapperSearch(ctx, class_obj, ctx->params, &err); if(err) return err; jsonObjectNode* cur; @@ -589,7 +616,7 @@ int dispatchCRUDMethod ( osrfMethodContext* ctx ) { osrfLogDebug(OSRF_LOG_MARK, "%s: Select qualifer set to [%s]", MODULENAME, _s); free(_s); - obj = doSearch(ctx, class_obj, _p, &err); + obj = doFieldmapperSearch(ctx, class_obj, _p, &err); if(err) return err; jsonObjectNode* cur; @@ -858,7 +885,7 @@ jsonObject* doCreate(osrfMethodContext* ctx, int* err ) { jsonNewObject(id) ); - jsonObject* list = doSearch( ctx,meta, fake_params, err); + jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err); if(*err) { jsonObjectFree( fake_params ); @@ -911,7 +938,7 @@ jsonObject* doRetrieve(osrfMethodContext* ctx, int* err ) { if (order_hash) jsonObjectPush(fake_params, jsonObjectClone(order_hash) ); - jsonObject* list = doSearch( ctx,meta, fake_params, err); + jsonObject* list = doFieldmapperSearch( ctx,meta, fake_params, err); if(*err) { jsonObjectFree( fake_params ); @@ -944,12 +971,13 @@ char* jsonNumberToDBString ( osrfHash* field, jsonObject* value ) { return pred; } -char* searchINPredicate (osrfHash* field, jsonObject* node, const char* op) { +char* searchINPredicate (const char* class, osrfHash* field, jsonObject* node, const char* op) { growing_buffer* sql_buf = buffer_init(32); buffer_fadd( sql_buf, - "%s ", + "\"%s\".%s ", + class, osrfHashGet(field, "name") ); @@ -1046,14 +1074,15 @@ char* searchValueTransform( jsonObject* array ) { return pred; } -char* searchFunctionPredicate (osrfHash* field, jsonObjectNode* node) { +char* searchFunctionPredicate (const char* class, osrfHash* field, jsonObjectNode* node) { growing_buffer* sql_buf = buffer_init(32); char* val = searchValueTransform(node->item); buffer_fadd( sql_buf, - "%s %s %s", + "\"%s\".%s %s %s", + class, osrfHashGet(field, "name"), node->key, val @@ -1066,15 +1095,16 @@ char* searchFunctionPredicate (osrfHash* field, jsonObjectNode* node) { return pred; } -char* searchFieldTransform (osrfHash* field, jsonObject* node) { +char* searchFieldTransform (const char* class, osrfHash* field, jsonObject* node) { growing_buffer* sql_buf = buffer_init(32); char* field_transform = jsonObjectToSimpleString( jsonObjectGetKey( node, "transform" ) ); buffer_fadd( sql_buf, - "%s(%s)", + "%s(\"%s\".%s)", field_transform, + class, osrfHashGet(field, "name") ); @@ -1085,10 +1115,10 @@ char* searchFieldTransform (osrfHash* field, jsonObject* node) { return pred; } -char* searchFieldTransformPredicate (osrfHash* field, jsonObjectNode* node) { +char* searchFieldTransformPredicate (const char* class, osrfHash* field, jsonObjectNode* node) { growing_buffer* sql_buf = buffer_init(32); - char* field_transform = searchFieldTransform( field, node->item ); + char* field_transform = searchFieldTransform( class, field, node->item ); char* value = NULL; if (jsonObjectGetKey( node->item, "value" )->type == JSON_ARRAY) { @@ -1121,7 +1151,7 @@ char* searchFieldTransformPredicate (osrfHash* field, jsonObjectNode* node) { return pred; } -char* searchSimplePredicate (const char* orig_op, osrfHash* field, jsonObject* node) { +char* searchSimplePredicate (const char* orig_op, const char* class, osrfHash* field, jsonObject* node) { char* val = NULL; @@ -1133,14 +1163,14 @@ char* searchSimplePredicate (const char* orig_op, osrfHash* field, jsonObject* n } } - char* pred = searchWriteSimplePredicate( field, osrfHashGet(field, "name"), orig_op, val ); + char* pred = searchWriteSimplePredicate( class, field, osrfHashGet(field, "name"), orig_op, val ); if (val) free(val); return pred; } -char* searchWriteSimplePredicate ( osrfHash* field, const char* left, const char* orig_op, const char* right ) { +char* searchWriteSimplePredicate ( const char* class, osrfHash* field, const char* left, const char* orig_op, const char* right ) { char* val = NULL; char* op = NULL; @@ -1167,7 +1197,7 @@ char* searchWriteSimplePredicate ( osrfHash* field, const char* left, const char } growing_buffer* sql_buf = buffer_init(16); - buffer_fadd( sql_buf, "%s %s %s", left, op, val ); + buffer_fadd( sql_buf, "\"%s\".%s %s %s", class, left, op, val ); free(val); free(op); @@ -1178,7 +1208,7 @@ char* searchWriteSimplePredicate ( osrfHash* field, const char* left, const char } -char* searchBETWEENPredicate (osrfHash* field, jsonObject* node) { +char* searchBETWEENPredicate (const char* class, osrfHash* field, jsonObject* node) { char* x_string; char* y_string; @@ -1209,25 +1239,25 @@ char* searchBETWEENPredicate (osrfHash* field, jsonObject* node) { return pred; } -char* searchPredicate ( osrfHash* field, jsonObject* node ) { +char* searchPredicate ( const char* class, osrfHash* field, jsonObject* node ) { char* pred = NULL; if (node->type == JSON_ARRAY) { // equality IN search - pred = searchINPredicate( field, node, NULL ); + pred = searchINPredicate( class, field, node, NULL ); } else if (node->type == JSON_HASH) { // non-equality search jsonObjectNode* pred_node; jsonObjectIterator* pred_itr = jsonNewObjectIterator( node ); while ( (pred_node = jsonObjectIteratorNext( pred_itr )) ) { if ( !(strcasecmp( pred_node->key,"between" )) ) - pred = searchBETWEENPredicate( field, pred_node->item ); + pred = searchBETWEENPredicate( class, field, pred_node->item ); else if ( !(strcasecmp( pred_node->key,"in" )) || !(strcasecmp( pred_node->key,"not in" )) ) - pred = searchINPredicate( field, pred_node->item, pred_node->key ); + pred = searchINPredicate( class, field, pred_node->item, pred_node->key ); else if ( pred_node->item->type == JSON_ARRAY ) - pred = searchFunctionPredicate( field, pred_node ); + pred = searchFunctionPredicate( class, field, pred_node ); else if ( pred_node->item->type == JSON_HASH ) - pred = searchFieldTransformPredicate( field, pred_node ); + pred = searchFieldTransformPredicate( class, field, pred_node ); else - pred = searchSimplePredicate( pred_node->key, field, pred_node->item ); + pred = searchSimplePredicate( pred_node->key, class, field, pred_node->item ); break; } @@ -1235,86 +1265,626 @@ char* searchPredicate ( osrfHash* field, jsonObject* node ) { growing_buffer* _p = buffer_init(16); buffer_fadd( _p, - "%s IS NULL", + "\"%s\".%s IS NULL", + class, osrfHashGet(field, "name") ); pred = buffer_data(_p); buffer_free(_p); } else { // equality search - pred = searchSimplePredicate( "=", field, node ); + pred = searchSimplePredicate( "=", class, field, node ); } return pred; } -char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) { - // XXX this will be used for joins, I think - //osrfHash* links = osrfHashGet(meta, "links"); +/* + +join : { + acn : { + field : record, + fkey : id + type : left + filter_op : or + filter : { ... }, + join : { + acp : { + field : call_number, + fkey : id, + filter : { ... }, + }, + }, + }, + mrd : { + field : record, + type : inner + fkey : id, + filter : { ... }, + } +} + +*/ - osrfHash* fields = osrfHashGet(meta, "fields"); - char* core_class = osrfHashGet(meta, "classname"); +char* searchJOIN ( jsonObject* join_hash, osrfHash* leftmeta ) { + + if (join_hash->type == JSON_STRING) { + char* __tmp = jsonObjectToSimpleString( join_hash ); + join_hash = jsonParseString("{}"); + jsonObjectSetKey(join_hash, __tmp, NULL); + free(__tmp); + } + + growing_buffer* join_buf = buffer_init(128); + char* leftclass = osrfHashGet(leftmeta, "classname"); - jsonObjectNode* node = NULL; jsonObjectNode* snode = NULL; - jsonObject* _tmp; + jsonObjectIterator* search_itr = jsonNewObjectIterator( join_hash ); + while ( (snode = jsonObjectIteratorNext( search_itr )) ) { + osrfHash* idlClass = osrfHashGet( oilsIDL(), snode->key ); + + char* class = osrfHashGet(idlClass, "classname"); + char* table = osrfHashGet(idlClass, "tablename"); + + char* type = jsonObjectToSimpleString( jsonObjectGetKey( snode->item, "type" ) ); + char* filter_op = jsonObjectToSimpleString( jsonObjectGetKey( snode->item, "filter_op" ) ); + char* fkey = jsonObjectToSimpleString( jsonObjectGetKey( snode->item, "fkey" ) ); + char* field = jsonObjectToSimpleString( jsonObjectGetKey( snode->item, "field" ) ); + + jsonObject* filter = jsonObjectGetKey( snode->item, "filter" ); + jsonObject* join_filter = jsonObjectGetKey( snode->item, "join" ); + + if (field && !fkey) { + fkey = (char*)oilsIDLFindPath("/%s/links/%s/key", class, field); + if (!fkey) { + osrfLogError( + OSRF_LOG_MARK, + "%s: JOIN failed. No link defined from %s.%s to %s", + MODULENAME, + class, + field, + leftclass + ); + buffer_free(join_buf); + return NULL; + } + fkey = strdup( fkey ); + + } else if (!field && fkey) { + field = (char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey ); + if (!field) { + osrfLogError( + OSRF_LOG_MARK, + "%s: JOIN failed. No link defined from %s.%s to %s", + MODULENAME, + leftclass, + fkey, + class + ); + buffer_free(join_buf); + return NULL; + } + field = strdup( field ); + + } else if (!field && !fkey) { + osrfHash* _links = oilsIDLFindPath("/%s/links", leftclass); + + int i = 0; + osrfStringArray* keys = osrfHashKeys( _links ); + while ( (fkey = osrfStringArrayGetString(keys, i++)) ) { + fkey = strdup(osrfStringArrayGetString(keys, i++)); + if ( !strcmp( (char*)oilsIDLFindPath("/%s/links/%s/class", leftclass, fkey), class) ) { + field = strdup( (char*)oilsIDLFindPath("/%s/links/%s/key", leftclass, fkey) ); + break; + } else { + free(fkey); + } + } + osrfStringArrayFree(keys); + + if (!field && !fkey) { + _links = oilsIDLFindPath("/%s/links", class); + + i = 0; + keys = osrfHashKeys( _links ); + while ( (field = osrfStringArrayGetString(keys, i++)) ) { + field = strdup(osrfStringArrayGetString(keys, i++)); + if ( !strcmp( (char*)oilsIDLFindPath("/%s/links/%s/class", class, field), class) ) { + fkey = strdup( (char*)oilsIDLFindPath("/%s/links/%s/key", class, field) ); + break; + } else { + free(field); + } + } + osrfStringArrayFree(keys); + } + + if (!field && !fkey) { + osrfLogError( + OSRF_LOG_MARK, + "%s: JOIN failed. No link defined between %s and %s", + MODULENAME, + leftclass, + class + ); + buffer_free(join_buf); + return NULL; + } + + } + + if (type) { + if ( !strcasecmp(type,"left") ) { + buffer_add(join_buf, " LEFT JOIN"); + } else if ( !strcasecmp(type,"right") ) { + buffer_add(join_buf, " RIGHT JOIN"); + } else if ( !strcasecmp(type,"full") ) { + buffer_add(join_buf, " FULL JOIN"); + } else { + buffer_add(join_buf, " INNER JOIN"); + } + } else { + buffer_add(join_buf, " INNER JOIN"); + } + + buffer_fadd(join_buf, " %s AS \"%s\" ON ( \"%s\".%s = \"%s\".%s", table, class, class, field, leftclass, fkey); + + if (filter) { + if (filter_op) { + if (!strcasecmp("or",filter_op)) { + buffer_add( join_buf, " OR " ); + } else { + buffer_add( join_buf, " AND " ); + } + } else { + buffer_add( join_buf, " AND " ); + } + + char* jpred = searchWHERE( filter, idlClass, 0 ); + buffer_fadd( join_buf, " %s", jpred ); + free(jpred); + } + + buffer_add(join_buf, " ) "); + + if (join_filter) { + char* jpred = searchJOIN( join_filter, idlClass ); + buffer_fadd( join_buf, " %s", jpred ); + free(jpred); + } + + free(type); + free(filter_op); + free(fkey); + free(field); + } + + char* join_string = buffer_data(join_buf); + buffer_free(join_buf); + return join_string; +} + +/* + +{ +class : { -or|-and : { field : { op : value }, ... }, ... }, ... } + +*/ +char* searchWHERE ( jsonObject* search_hash, osrfHash* meta, int opjoin_type ) { growing_buffer* sql_buf = buffer_init(128); - buffer_add(sql_buf, "SELECT"); + + jsonObjectNode* node = NULL; int first = 1; - if ( (_tmp = jsonObjectGetKey( order_hash, "select" )) ) { + jsonObjectIterator* search_itr = jsonNewObjectIterator( search_hash ); + while ( (node = jsonObjectIteratorNext( search_itr )) ) { - jsonObjectIterator* class_itr = jsonNewObjectIterator( _tmp ); - while ( (snode = jsonObjectIteratorNext( class_itr )) ) { + if (first) { + first = 0; + } else { + if (opjoin_type == 1) buffer_add(sql_buf, " OR "); + else buffer_add(sql_buf, " AND "); + } + + if ( !strncmp("+",node->key,1) ) { + char* subpred = searchWHERE( node->item, osrfHashGet( oilsIDL(), node->key + 1 ), 0); + buffer_fadd(sql_buf, "( %s )", subpred); + free(subpred); + } else if ( !strcasecmp("-or",node->key) ) { + char* subpred = searchWHERE( node->item, meta, 1); + buffer_fadd(sql_buf, "( %s )", subpred); + free(subpred); + } else if ( !strcasecmp("-and",node->key) ) { + char* subpred = searchWHERE( node->item, meta, 0); + buffer_fadd(sql_buf, "( %s )", subpred); + free(subpred); + } else { + + char* class = osrfHashGet(meta, "classname"); + osrfHash* fields = osrfHashGet(meta, "fields"); + osrfHash* field = osrfHashGet( fields, node->key ); + + if (!field) { + osrfLogError( + OSRF_LOG_MARK, + "%s: Attempt to reference non-existant column %s on table %s", + MODULENAME, + node->key, + osrfHashGet(meta, "tablename") + ); + buffer_free(sql_buf); + return NULL; + } - osrfHash* idlClass = osrfHashGet( idl, snode->key ); - if (!idlClass) continue; - char* cname = osrfHashGet(idlClass, "classname"); + char* subpred = searchPredicate( class, field, node->item ); + buffer_add( sql_buf, subpred ); + free(subpred); + } + } - if (strcmp(core_class,snode->key)) continue; + jsonObjectIteratorFree(search_itr); - jsonObjectIterator* select_itr = jsonNewObjectIterator( snode->item ); - while ( (node = jsonObjectIteratorNext( select_itr )) ) { - osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), jsonObjectToSimpleString(node->item) ); - char* fname = osrfHashGet(field, "name"); + char* pred = buffer_data(sql_buf); + buffer_free(sql_buf); + + return pred; +} + +char* SELECT ( + /* method context */ osrfMethodContext* ctx, + + /* SELECT */ jsonObject* selhash, + /* FROM */ jsonObject* join_hash, + /* WHERE */ jsonObject* search_hash, + /* ORDER BY */ jsonObject* order_hash, + /* LIMIT */ jsonObject* limit, + /* OFFSET */ jsonObject* offset, + /* flags */ int flags +) { + // in case we don't get a select list + jsonObject* defaultselhash = NULL; + + // general tmp objects + jsonObject* __tmp = NULL; + jsonObjectNode* selclass = NULL; + jsonObjectNode* selfield = NULL; + jsonObjectNode* snode = NULL; + jsonObjectNode* onode = NULL; + jsonObject* found = NULL; + + char* string = NULL; + int first = 1; + int gfirst = 1; + //int hfirst = 1; + + // return variable for the SQL + char* sql = NULL; + + // the core search class + char* core_class = NULL; + + // metadata about the core search class + osrfHash* core_meta = NULL; + osrfHash* core_fields = NULL; + osrfHash* idlClass = NULL; + + // the query buffer + growing_buffer* sql_buf = buffer_init(128); + // temp buffer for the SELECT list + growing_buffer* select_buf = buffer_init(128); + growing_buffer* order_buf = buffer_init(128); + growing_buffer* group_buf = buffer_init(128); + growing_buffer* having_buf = buffer_init(128); + + // punt if there's no core class + if (!join_hash || ( join_hash->type == JSON_HASH && !join_hash->size )) + return NULL; + + // get the core class -- the only key of the top level FROM clause, or a string + if (join_hash->type == JSON_HASH) { + jsonObjectIterator* tmp_itr = jsonNewObjectIterator( join_hash ); + snode = jsonObjectIteratorNext( tmp_itr ); + + core_class = strdup( snode->key ); + join_hash = snode->item; + + jsonObjectIteratorFree( tmp_itr ); + snode = NULL; + + } else if (join_hash->type == JSON_STRING) { + core_class = jsonObjectToSimpleString( join_hash ); + join_hash = NULL; + } + + // punt if we don't know about the core class + if (!(core_meta = osrfHashGet( oilsIDL(), core_class ))) + return NULL; + + core_fields = osrfHashGet(core_meta, "fields"); + + // if the select list is empty, or the core class field list is '*', + // build the default select list ... + if (!selhash) { + selhash = defaultselhash = jsonParseString( "{}" ); + jsonObjectSetKey( selhash, core_class, jsonParseString( "[]" ) ); + } else if ( (__tmp = jsonObjectGetKey( selhash, core_class )) && __tmp->type == JSON_STRING ) { + char* __x = jsonObjectToSimpleString( __tmp ); + if (!strncmp( "*", __x, 1 )) { + jsonObjectRemoveKey( selhash, core_class ); + jsonObjectSetKey( selhash, core_class, jsonParseString( "[]" ) ); + } + free(__x); + } + + // ... and if we /are/ building the default list, do that + if ( (__tmp = jsonObjectGetKey(selhash,core_class)) && !__tmp->size ) { + + int i = 0; + char* field; + + osrfStringArray* keys = osrfHashKeys( core_fields ); + while ( (field = osrfStringArrayGetString(keys, i++)) ) { + if ( strncasecmp( "true", osrfHashGet( osrfHashGet( core_fields, field ), "virtual" ), 4 ) ) + jsonObjectPush( __tmp, jsonNewObject( field ) ); + } + osrfStringArrayFree(keys); + } + + // Now we build the acutal select list + int sel_pos = 1; + jsonObject* is_agg = jsonObjectFindPath(selhash, "//aggregate"); + first = 1; + gfirst = 1; + jsonObjectIterator* selclass_itr = jsonNewObjectIterator( selhash ); + while ( (selclass = jsonObjectIteratorNext( selclass_itr )) ) { + + // round trip through the idl, just to be safe + idlClass = osrfHashGet( oilsIDL(), selclass->key ); + if (!idlClass) continue; + char* cname = osrfHashGet(idlClass, "classname"); + + // make sure the target relation is in the join tree + if (strcmp(core_class,cname)) { + if (!join_hash) continue; + + if (join_hash->type == JSON_STRING) { + string = jsonObjectToSimpleString(join_hash); + found = strcmp(string,cname) ? NULL : jsonParseString("{\"1\":\"1\"}"); + free(string); + } else { + found = jsonObjectFindPath(join_hash, "//%s", cname); + } + + if (!found->size) { + jsonObjectFree(found); + continue; + } + + jsonObjectFree(found); + } + + // stitch together the column list ... + jsonObjectIterator* select_itr = jsonNewObjectIterator( selclass->item ); + while ( (selfield = jsonObjectIteratorNext( select_itr )) ) { + + char* __column = NULL; + char* __alias = NULL; + + // ... if it's a sstring, just toss it on the pile + if (selfield->item->type == JSON_STRING) { + + // again, just to be safe + char* _requested_col = jsonObjectToSimpleString(selfield->item); + osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), _requested_col ); + free(_requested_col); + + if (!field) continue; + __column = strdup(osrfHashGet(field, "name")); + + if (first) { + first = 0; + } else { + buffer_add(select_buf, ","); + } + + buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, __column, __column); + + // ... but it could be an object, in which case we check for a Field Transform + } else { + + __column = jsonObjectToSimpleString( jsonObjectGetKey( selfield->item, "column" ) ); + + // again, just to be safe + osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), __column ); if (!field) continue; + char* fname = osrfHashGet(field, "name"); if (first) { first = 0; } else { - buffer_add(sql_buf, ","); + buffer_add(select_buf, ","); + } + + if ((__tmp = jsonObjectGetKey( selfield->item, "alias" ))) { + __alias = jsonObjectToSimpleString( __tmp ); + } else { + __alias = strdup(__column); } - buffer_fadd(sql_buf, " \"%s\".%s", cname, fname, cname, fname); + if (jsonObjectGetKey( selfield->item, "transform" )) { + free(__column); + __column = searchFieldTransform(cname, field, selfield->item); + buffer_fadd(select_buf, " %s AS \"%s\"", __column, __alias); + } else { + buffer_fadd(select_buf, " \"%s\".%s AS \"%s\"", cname, fname, __alias); + } } + + if (is_agg->size || (flags & SELECT_DISTINCT)) { + + if (!jsonBoolIsTrue( jsonObjectGetKey( selfield->item, "aggregate" ) )) { + if (gfirst) { + gfirst = 0; + } else { + buffer_add(group_buf, ","); + } + + buffer_fadd(group_buf, " %d", sel_pos); + /* + } else if (is_agg = jsonObjectGetKey( selfield->item, "having" )) { + if (gfirst) { + gfirst = 0; + } else { + buffer_add(group_buf, ","); + } + + __column = searchFieldTransform(cname, field, selfield->item); + buffer_fadd(group_buf, " %s", __column); + __column = searchFieldTransform(cname, field, selfield->item); + */ + } + } + + if (__column) free(__column); + if (__alias) free(__alias); + + sel_pos++; } + } - if (first) buffer_add(sql_buf, " *"); + if (is_agg) jsonObjectFree(is_agg); - } else { - buffer_add(sql_buf, " *"); + char* col_list = buffer_data(select_buf); + buffer_free(select_buf); + + // Put it all together + buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\" ", col_list, osrfHashGet(core_meta, "tablename"), core_class ); + free(col_list); + + // Now, walk the join tree and add that clause + if ( join_hash ) { + char* join_clause = searchJOIN( join_hash, core_meta ); + buffer_add(sql_buf, join_clause); + free(join_clause); } - buffer_fadd(sql_buf, " FROM %s AS \"%s\" WHERE ", osrfHashGet(meta, "tablename"), core_class ); + if ( search_hash ) { + buffer_add(sql_buf, " WHERE "); + // and it's on the the WHERE clause + char* pred = searchWHERE( search_hash, core_meta, 0 ); + if (!pred) { + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Severe query error -- see error log for more details" + ); + free(core_class); + buffer_free(sql_buf); + if (defaultselhash) jsonObjectFree(defaultselhash); + return NULL; + } else { + buffer_add(sql_buf, pred); + free(pred); + } + } - char* pred; first = 1; - jsonObjectIterator* search_itr = jsonNewObjectIterator( search_hash ); - while ( (node = jsonObjectIteratorNext( search_itr )) ) { - osrfHash* field = osrfHashGet( fields, node->key ); + jsonObjectIterator* class_itr = jsonNewObjectIterator( order_hash ); + while ( (snode = jsonObjectIteratorNext( class_itr )) ) { - if (!field) { - osrfLogError( - OSRF_LOG_MARK, - "%s: Attempt to reference non-existant column %s on table %s", - MODULENAME, - node->key, - osrfHashGet(meta, "tablename") - ); + if (!jsonObjectGetKey(selhash,snode->key)) + continue; + + if ( snode->item->type == JSON_HASH ) { + + jsonObjectIterator* order_itr = jsonNewObjectIterator( snode->item ); + while ( (onode = jsonObjectIteratorNext( order_itr )) ) { + + if (!oilsIDLFindPath( "/%s/fields/%s", snode->key, onode->key )) + continue; + + char* direction = NULL; + if ( onode->item->type == JSON_HASH ) { + if ( jsonObjectGetKey( onode->item, "transform" ) ) { + string = searchFieldTransform( + snode->key, + oilsIDLFindPath( "/%s/fields/%s", snode->key, onode->key ), + onode->item + ); + } else { + growing_buffer* field_buf = buffer_init(16); + buffer_fadd(field_buf, "\"%s\".%s", snode->key, onode->key); + string = buffer_data(field_buf); + buffer_free(field_buf); + } + + if ( (__tmp = jsonObjectGetKey( onode->item, "direction" )) ) { + direction = jsonObjectToSimpleString(__tmp); + if (!strncasecmp(direction, "d", 1)) { + free(direction); + direction = " DESC"; + } else { + free(direction); + direction = " ASC"; + } + } + + } else { + string = strdup(onode->key); + direction = jsonObjectToSimpleString(onode->item); + if (!strncasecmp(direction, "d", 1)) { + free(direction); + direction = " DESC"; + } else { + free(direction); + direction = " ASC"; + } + } + + if (first) { + first = 0; + } else { + buffer_add(order_buf, ", "); + } + + buffer_add(order_buf, string); + free(string); + + if (direction) { + buffer_add(order_buf, direction); + } + + } + + } else if ( snode->item->type == JSON_ARRAY ) { + + jsonObjectIterator* order_itr = jsonNewObjectIterator( snode->item ); + while ( (onode = jsonObjectIteratorNext( order_itr )) ) { + + char* _f = jsonObjectToSimpleString( onode->item ); + + if (!oilsIDLFindPath( "/%s/fields/%s", snode->key, _f)) + continue; + + if (first) { + first = 0; + } else { + buffer_add(order_buf, ", "); + } + + buffer_add(order_buf, _f); + free(_f); + + } + + // IT'S THE OOOOOOOOOOOLD STYLE! + } else { + osrfLogError(OSRF_LOG_MARK, "%s: Possible SQL injection attempt; direct order by is not allowed", MODULENAME); osrfAppSessionStatus( ctx->session, OSRF_STATUS_INTERNALSERVERERROR, @@ -1322,32 +1892,276 @@ char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* m ctx->request, "Severe query error -- see error log for more details" ); + + free(core_class); + buffer_free(order_buf); buffer_free(sql_buf); + if (defaultselhash) jsonObjectFree(defaultselhash); return NULL; } - if (first) { - first = 0; - } else { - buffer_add(sql_buf, " AND "); + } + + string = buffer_data(group_buf); + buffer_free(group_buf); + + if (strlen(string)) { + buffer_fadd( + sql_buf, + " GROUP BY %s", + string + ); + } + + free(string); + + string = buffer_data(having_buf); + buffer_free(having_buf); + + if (strlen(string)) { + buffer_fadd( + sql_buf, + " HAVING %s", + string + ); + } + + free(string); + + string = buffer_data(order_buf); + buffer_free(order_buf); + + if (strlen(string)) { + buffer_fadd( + sql_buf, + " ORDER BY %s", + string + ); + } + + free(string); + + if ( limit ){ + string = jsonObjectToSimpleString(limit); + buffer_fadd( sql_buf, " LIMIT %d", atoi(string) ); + free(string); + } + + if (offset) { + string = jsonObjectToSimpleString(offset); + buffer_fadd( sql_buf, " OFFSET %d", atoi(string) ); + free(string); + } + + buffer_add(sql_buf, ";"); + + sql = buffer_data(sql_buf); + + free(core_class); + buffer_free(sql_buf); + if (defaultselhash) jsonObjectFree(defaultselhash); + + return sql; + +} + +char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* meta, osrfMethodContext* ctx ) { + + osrfHash* fields = osrfHashGet(meta, "fields"); + char* core_class = osrfHashGet(meta, "classname"); + + jsonObject* join_hash = jsonObjectGetKey( order_hash, "join" ); + + jsonObjectNode* node = NULL; + jsonObjectNode* snode = NULL; + jsonObjectNode* onode = NULL; + jsonObject* _tmp = NULL; + jsonObject* selhash = NULL; + jsonObject* defaultselhash = NULL; + + growing_buffer* sql_buf = buffer_init(128); + growing_buffer* select_buf = buffer_init(128); + + if ( !(selhash = jsonObjectGetKey( order_hash, "select" )) ) { + defaultselhash = jsonParseString( "{}" ); + selhash = defaultselhash; + } + + if ( !jsonObjectGetKey(selhash,core_class) ) { + jsonObjectSetKey( selhash, core_class, jsonParseString( "[]" ) ); + jsonObject* flist = jsonObjectGetKey( selhash, core_class ); + + int i = 0; + char* field; + + osrfStringArray* keys = osrfHashKeys( fields ); + while ( (field = osrfStringArrayGetString(keys, i++)) ) { + if ( strcasecmp( "true", osrfHashGet( osrfHashGet( fields, field ), "virtual" ) ) ) + jsonObjectPush( flist, jsonNewObject( field ) ); } + osrfStringArrayFree(keys); + } - pred = searchPredicate( field, node->item); - buffer_fadd( sql_buf, "%s", pred ); - free(pred); + int first = 1; + jsonObjectIterator* class_itr = jsonNewObjectIterator( selhash ); + while ( (snode = jsonObjectIteratorNext( class_itr )) ) { + + osrfHash* idlClass = osrfHashGet( oilsIDL(), snode->key ); + if (!idlClass) continue; + char* cname = osrfHashGet(idlClass, "classname"); + + if (strcmp(core_class,snode->key)) { + if (!join_hash) continue; + + jsonObject* found = jsonObjectFindPath(join_hash, "//%s", snode->key); + if (!found->size) { + jsonObjectFree(found); + continue; + } + + jsonObjectFree(found); + } + + jsonObjectIterator* select_itr = jsonNewObjectIterator( snode->item ); + while ( (node = jsonObjectIteratorNext( select_itr )) ) { + osrfHash* field = osrfHashGet( osrfHashGet( idlClass, "fields" ), jsonObjectToSimpleString(node->item) ); + char* fname = osrfHashGet(field, "name"); + + if (!field) continue; + + if (first) { + first = 0; + } else { + buffer_add(select_buf, ","); + } + + buffer_fadd(select_buf, " \"%s\".%s", cname, fname); + } } - jsonObjectIteratorFree(search_itr); + char* col_list = buffer_data(select_buf); + buffer_free(select_buf); - if (order_hash) { - char* string; - if ( (_tmp = jsonObjectGetKey( jsonObjectGetKey( order_hash, "order_by" ), core_class ) ) ){ - string = jsonObjectToSimpleString(_tmp); - buffer_fadd( - sql_buf, - " ORDER BY %s", - string + buffer_fadd(sql_buf, "SELECT %s FROM %s AS \"%s\"", col_list, osrfHashGet(meta, "tablename"), core_class ); + + if ( join_hash ) { + char* join_clause = searchJOIN( join_hash, meta ); + buffer_fadd(sql_buf, " %s", join_clause); + free(join_clause); + } + + buffer_add(sql_buf, " WHERE "); + + char* pred = searchWHERE( search_hash, meta, 0 ); + if (!pred) { + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Severe query error -- see error log for more details" ); + buffer_free(sql_buf); + return NULL; + } else { + buffer_add(sql_buf, pred); + free(pred); + } + + if (order_hash) { + char* string = NULL; + if ( (_tmp = jsonObjectGetKey( order_hash, "order_by" )) ){ + + growing_buffer* order_buf = buffer_init(128); + + first = 1; + jsonObjectIterator* class_itr = jsonNewObjectIterator( _tmp ); + while ( (snode = jsonObjectIteratorNext( class_itr )) ) { + + if (!jsonObjectGetKey(selhash,snode->key)) + continue; + + if ( snode->item->type == JSON_HASH ) { + + jsonObjectIterator* order_itr = jsonNewObjectIterator( snode->item ); + while ( (onode = jsonObjectIteratorNext( order_itr )) ) { + + if (!oilsIDLFindPath( "/%s/fields/%s", snode->key, onode->key )) + continue; + + char* direction = NULL; + if ( onode->item->type == JSON_HASH ) { + if ( jsonObjectGetKey( onode->item, "transform" ) ) { + string = searchFieldTransform( + snode->key, + oilsIDLFindPath( "/%s/fields/%s", snode->key, onode->key ), + onode->item + ); + } else { + growing_buffer* field_buf = buffer_init(16); + buffer_fadd(field_buf, "\"%s\".%s", snode->key, onode->key); + string = buffer_data(field_buf); + buffer_free(field_buf); + } + + if ( (_tmp = jsonObjectGetKey( onode->item, "direction" )) ) { + direction = jsonObjectToSimpleString(_tmp); + if (!strncasecmp(direction, "d", 1)) { + free(direction); + direction = " DESC"; + } else { + free(direction); + direction = " ASC"; + } + } + + } else { + string = strdup(onode->key); + direction = jsonObjectToSimpleString(onode->item); + if (!strncasecmp(direction, "d", 1)) { + free(direction); + direction = " DESC"; + } else { + free(direction); + direction = " ASC"; + } + } + + if (first) { + first = 0; + } else { + buffer_add(order_buf, ", "); + } + + buffer_add(order_buf, string); + free(string); + + if (direction) { + buffer_add(order_buf, direction); + } + + } + + } else { + string = jsonObjectToSimpleString(snode->item); + buffer_add(order_buf, string); + free(string); + break; + } + + } + + string = buffer_data(order_buf); + buffer_free(order_buf); + + if (strlen(string)) { + buffer_fadd( + sql_buf, + " ORDER BY %s", + string + ); + } + free(string); } @@ -1377,11 +2191,79 @@ char* buildSELECT ( jsonObject* search_hash, jsonObject* order_hash, osrfHash* m char* sql = buffer_data(sql_buf); buffer_free(sql_buf); + if (defaultselhash) jsonObjectFree(defaultselhash); return sql; } -jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* params, int* err ) { +int doJSONSearch ( osrfMethodContext* ctx ) { + OSRF_METHOD_VERIFY_CONTEXT(ctx); + osrfLogDebug(OSRF_LOG_MARK, "Recieved query request"); + + int err = 0; + + // XXX for now... + dbhandle = writehandle; + + jsonObject* hash = jsonObjectGetIndex(ctx->params, 0); + + osrfLogDebug(OSRF_LOG_MARK, "Building SQL ..."); + char* sql = SELECT( + ctx, + jsonObjectGetKey( hash, "select" ), + jsonObjectGetKey( hash, "from" ), + jsonObjectGetKey( hash, "where" ), + jsonObjectGetKey( hash, "order_by" ), + jsonObjectGetKey( hash, "limit" ), + jsonObjectGetKey( hash, "offset" ), + jsonBoolIsTrue(jsonObjectGetKey( hash, "distinct" )) ? SELECT_DISTINCT : 0 + ); + + if (!sql) { + err = -1; + return err; + } + + osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql); + dbi_result result = dbi_conn_query(dbhandle, sql); + + if(result) { + osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors"); + + if (dbi_result_first_row(result)) { + /* JSONify the result */ + osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row"); + + do { + osrfAppRespond( ctx, oilsMakeJSONFromResult( result ) ); + } while (dbi_result_next_row(result)); + + } else { + osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql); + } + + osrfAppRespondComplete( ctx, NULL ); + + /* clean up the query */ + dbi_result_free(result); + + } else { + err = -1; + osrfLogError(OSRF_LOG_MARK, "%s: Error with query [%s]", MODULENAME, sql); + osrfAppSessionStatus( + ctx->session, + OSRF_STATUS_INTERNALSERVERERROR, + "osrfMethodException", + ctx->request, + "Severe query error -- see error log for more details" + ); + } + + free(sql); + return err; +} + +jsonObject* doFieldmapperSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* params, int* err ) { // XXX for now... dbhandle = writehandle; @@ -1389,6 +2271,7 @@ jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* param osrfHash* links = osrfHashGet(meta, "links"); osrfHash* fields = osrfHashGet(meta, "fields"); char* core_class = osrfHashGet(meta, "classname"); + char* pkey = osrfHashGet(meta, "primarykey"); jsonObject* _tmp; jsonObject* obj; @@ -1404,6 +2287,7 @@ jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* param osrfLogDebug(OSRF_LOG_MARK, "%s SQL = %s", MODULENAME, sql); dbi_result result = dbi_conn_query(dbhandle, sql); + osrfHash* dedup = osrfNewHash(); jsonObject* res_list = jsonParseString("[]"); if(result) { osrfLogDebug(OSRF_LOG_MARK, "Query returned with no errors"); @@ -1412,8 +2296,15 @@ jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* param /* JSONify the result */ osrfLogDebug(OSRF_LOG_MARK, "Query returned at least one row"); do { - obj = oilsMakeJSONFromResult( result, meta ); - jsonObjectPush(res_list, obj); + obj = oilsMakeFieldmapperFromResult( result, meta ); + int pkey_pos = atoi( osrfHashGet( osrfHashGet( fields, pkey ), "array_position" ) ); + char* pkey_val = jsonObjectToSimpleString( jsonObjectGetIndex( obj, pkey_pos ) ); + if ( osrfHashGet( dedup, pkey_val ) ) { + jsonObjectFree(obj); + } else { + osrfHashSet( dedup, pkey_val, pkey_val ); + jsonObjectPush(res_list, obj); + } } while (dbi_result_next_row(result)); } else { osrfLogDebug(OSRF_LOG_MARK, "%s returned no results for query %s", MODULENAME, sql); @@ -1489,7 +2380,7 @@ jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* param osrfHash* value_field = field; - osrfHash* kid_idl = osrfHashGet(idl, osrfHashGet(kid_link, "class")); + osrfHash* kid_idl = osrfHashGet(oilsIDL(), osrfHashGet(kid_link, "class")); if (!kid_idl) continue; if (!(strcmp( osrfHashGet(kid_link, "reltype"), "has_many" ))) { // has_many @@ -1578,7 +2469,7 @@ jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* param ); } - jsonObject* kids = doSearch(ctx, kid_idl, fake_params, err); + jsonObject* kids = doFieldmapperSearch(ctx, kid_idl, fake_params, err); if(*err) { jsonObjectFree( fake_params ); @@ -1608,7 +2499,7 @@ jsonObject* doSearch ( osrfMethodContext* ctx, osrfHash* meta, jsonObject* param osrfHashGet( osrfHashGet( osrfHashGet( - idl, + oilsIDL(), osrfHashGet(kid_link, "class") ), "fields" @@ -1913,7 +2804,7 @@ jsonObject* doDelete(osrfMethodContext* ctx, int* err ) { } -jsonObject* oilsMakeJSONFromResult( dbi_result result, osrfHash* meta) { +jsonObject* oilsMakeFieldmapperFromResult( dbi_result result, osrfHash* meta) { if(!(result && meta)) return jsonNULL; jsonObject* object = jsonParseString("[]"); @@ -2022,4 +2913,82 @@ jsonObject* oilsMakeJSONFromResult( dbi_result result, osrfHash* meta) { return object; } +jsonObject* oilsMakeJSONFromResult( dbi_result result ) { + if(!result) return jsonNULL; + + jsonObject* object = jsonParseString("{}"); + + time_t _tmp_dt; + char dt_string[256]; + struct tm gmdt; + + int fmIndex; + int columnIndex = 1; + int attr; + unsigned short type; + const char* columnName; + + /* cycle through the column list */ + while( (columnName = dbi_result_get_field_name(result, columnIndex++)) ) { + + osrfLogInternal(OSRF_LOG_MARK, "Looking for column named [%s]...", (char*)columnName); + + fmIndex = -1; // reset the position + + /* determine the field type and storage attributes */ + type = dbi_result_get_field_type(result, columnName); + attr = dbi_result_get_field_attribs(result, columnName); + + if (dbi_result_field_is_null(result, columnName)) { + jsonObjectSetKey( object, columnName, jsonNewObject(NULL) ); + } else { + + switch( type ) { + + case DBI_TYPE_INTEGER : + + if( attr & DBI_INTEGER_SIZE8 ) + jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_longlong(result, columnName)) ); + else + jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_long(result, columnName)) ); + break; + + case DBI_TYPE_DECIMAL : + jsonObjectSetKey( object, columnName, jsonNewNumberObject(dbi_result_get_double(result, columnName)) ); + break; + + case DBI_TYPE_STRING : + jsonObjectSetKey( object, columnName, jsonNewObject(dbi_result_get_string(result, columnName)) ); + break; + + case DBI_TYPE_DATETIME : + + memset(dt_string, '\0', 256); + memset(&gmdt, '\0', sizeof(gmdt)); + memset(&_tmp_dt, '\0', sizeof(_tmp_dt)); + + _tmp_dt = dbi_result_get_datetime(result, columnName); + + localtime_r( &_tmp_dt, &gmdt ); + + if (!(attr & DBI_DATETIME_DATE)) { + strftime(dt_string, 255, "%T", &gmdt); + } else if (!(attr & DBI_DATETIME_TIME)) { + strftime(dt_string, 255, "%F", &gmdt); + } else { + strftime(dt_string, 255, "%FT%T%z", &gmdt); + } + + jsonObjectSetKey( object, columnName, jsonNewObject(dt_string) ); + break; + + case DBI_TYPE_BINARY : + osrfLogError( OSRF_LOG_MARK, + "Can't do binary at column %s : index %d", columnName, columnIndex - 1); + } + } + } + + return object; +} diff --git a/Open-ILS/src/cgi-bin/lib-setup.cgi b/Open-ILS/src/cgi-bin/lib-setup.cgi index 1c67ba3a6d..3d0cb1a77c 100755 --- a/Open-ILS/src/cgi-bin/lib-setup.cgi +++ b/Open-ILS/src/cgi-bin/lib-setup.cgi @@ -23,9 +23,9 @@ my $cgi = new CGI; # setup part #------------------------------------------------------------------------------- -my %org_cols = ( qw/id SysID name Name parent_ou Parent ou_type OrgUnitType shortname ShortName email Email phone Phone/ ); +my %org_cols = ( qw/id SysID name Name parent_ou Parent ou_type OrgUnitType shortname ShortName email Email phone Phone opac_visible OPACVisible/ ); -my @col_display_order = ( qw/id name shortname ou_type email phone parent_ou/ ); +my @col_display_order = ( qw/id name shortname ou_type email phone opac_visible parent_ou/ ); if (my $action = $cgi->param('action')) { if ( $action eq 'Update' ) { @@ -248,12 +248,27 @@ if (my $action = $cgi->param('action')) { th($org_cols{email}), td("email() ."\">"), ); + print Tr( th($org_cols{phone}), td(""), ); print Tr( + th($org_cols{opac_visible} .'*'), + td("" + ), + ); + + print Tr( th($org_cols{parent_ou}), td("" ); - print ""; + print "*". + "You must hide every OU you want hidden, not just an anscestor!"; #------------------------------------------------------------------------- # Hours of operation form diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml index 41077d182e..a19fa223b2 100644 --- a/Open-ILS/src/extras/ils_events.xml +++ b/Open-ILS/src/extras/ils_events.xml @@ -140,6 +140,17 @@ The copy in question is not in an ideal status for deleting + + The requested bib record is marked as deleted + + + The requested volume is marked as deleted + + + The saved item has been edited by another user + + + @@ -564,6 +575,12 @@ A duplicate money.collections_tracker object already exists in the database + + User has reached the maximum number of holds + + + User has already created a bucket with the requested name + diff --git a/Open-ILS/src/extras/org_tree_html_options.pl b/Open-ILS/src/extras/org_tree_html_options.pl index fce9b14f64..abde8ba724 100644 --- a/Open-ILS/src/extras/org_tree_html_options.pl +++ b/Open-ILS/src/extras/org_tree_html_options.pl @@ -6,7 +6,7 @@ use OpenSRF::System; use OpenILS::Utils::Fieldmapper; use OpenSRF::Utils::SettingsClient; -die "usage: perl org_tree_js.pl " unless $ARGV[1]; +die "usage: perl org_tree_html_options.pl " unless $ARGV[1]; OpenSRF::System->bootstrap_client(config_file => $ARGV[0]); open FILE, ">$ARGV[1]"; @@ -25,6 +25,7 @@ close FILE; sub print_option { my $node = shift; + return unless ($node->opac_visible =~ /^[y1t]+/i); my $depth = $node->ou_type - 1; my $sname = $node->shortname; my $name = $node->name; diff --git a/Open-ILS/src/extras/org_tree_js.pl b/Open-ILS/src/extras/org_tree_js.pl index c09480974b..f7f74aa88f 100644 --- a/Open-ILS/src/extras/org_tree_js.pl +++ b/Open-ILS/src/extras/org_tree_js.pl @@ -23,8 +23,8 @@ my $pile = "var _l = ["; my @array; for my $o (@$tree) { - my ($i,$t,$p,$n) = ($o->id,$o->ou_type,$o->parent_ou,$o->name); - push @array, "[$i,$t,$p,\"$n\"]"; + my ($i,$t,$p,$n,$v) = ($o->id,$o->ou_type,$o->parent_ou,$o->name,$o->opac_visible); + push @array, "[$i,$t,$p,\"$n\",\"$v\"]"; } $pile .= join ',', @array; $pile .= <= 50 ) + result.events.push('MAX_HOLDS'); +} else { + log_info("This is a staff-placed hold"); +} + + if( isTrue(patron.barred) ) result.events.push('PATRON_BARRED'); @@ -15,15 +31,18 @@ if( !isTrue(copy.circulate) ) /* all STATELIB items are holdable regardless of type */ if( isOrgDescendent('STATELIB', copy.circ_lib.id) ) return; + var mod = (copy.circ_modifier) ? copy.circ_modifier.toLowerCase() : ""; +var marcItemType = getMARCItemType(); + log_info("circ-modifier = "+mod); +log_info("marc-type = "+marcItemType); + if( mod == 'bestsellernh' ) result.events.push('ITEM_NOT_HOLDABLE'); -var marcItemType = getMARCItemType(); -var isAnc; if( ( marcItemType == 'g' || marcItemType == 'i' || @@ -32,6 +51,7 @@ if( ( marcItemType == 'g' || mod == 'music' || mod == 'audiobook' || mod == 'av' || + mod == 'new-av' || mod == 'cd' || mod == 'kit' || mod == 'dvd' || @@ -46,25 +66,43 @@ if( ( marcItemType == 'g' || mod == 'video-long' || mod == 'video' ) ) { - isAnc = hasCommonAncestor( copy.circ_lib.id, patron.home_ou.id, 1 ); - if( isAnc) { - log_info("patron and copy circ_lib share a common ancestor, hold allowed"); + log_info("this is a range-protected item..."); - } else { - log_info("patron and copy circ_lib do NOT share a common ancestor"); + if( ! hasCommonAncestor( volume.owning_lib, holdPickupLib, 1 ) ) { - if( hasCommonAncestor( copy.circ_lib.id, holdRequestLib.id, 1) ) { - log_info("request_lib and copy circ_lib DO share a common ancestor"); + /* we don't want these items to transit to the pickup lib */ + result.events.push('ITEM_NOT_HOLDABLE'); + log_info("pickup_lib is not in the owning_lib's region...NOT OK"); - } else { + } else { /* pickup lib is in the owning region */ - log_info("request_lib and copy circ_lib also do NOT share a common ancestor, hold on this type of material not allowed"); - result.events.push('ITEM_NOT_HOLDABLE'); - } - } + if( isStaffHold && hasCommonAncestor( volume.owning_lib, holdRequestLib.id, 1) ) { + + /* staff in the region can place holds for patrons outside the region with local pickup lib */ + log_info("local, staff-placed hold is allowed with local pickup_lib...OK"); + + } else { + + if( hasCommonAncestor( volume.owning_lib, patron.home_ou.id, 1 ) ) { + + /* patrons can hold the item if they are registered + in the region and pickup lib is local */ + log_info("patron's home_ou is in the owning region...OK"); + + } else { + + log_info("patron's home_ou lies outside the owning region...NOT OK"); + result.events.push('ITEM_NOT_HOLDABLE'); + } + } + } } + + } go(); + + diff --git a/Open-ILS/src/offline/offline.pl b/Open-ILS/src/offline/offline.pl index f7262b69d1..192124b438 100755 --- a/Open-ILS/src/offline/offline.pl +++ b/Open-ILS/src/offline/offline.pl @@ -645,6 +645,11 @@ sub ol_handle_checkout { 'INTERNAL_SERVER_ERROR', payload => 'TOO MANY NON CATS'); } + if( $args->{barcode} ) { + my( $c, $e ) = $U->fetch_copy_by_barcode($args->{barcode}); + return $e if $e; + } + return $U->simplereq( 'open-ils.circ', 'open-ils.circ.checkout', $authtoken, $args ); } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm index 92879eb005..b7d7e46ad4 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm @@ -117,7 +117,7 @@ __PACKAGE__->register_method( api_name => "open-ils.actor.patron.settings.retrieve", ); sub user_settings { - my( $self, $client, $user_session, $uid ) = @_; + my( $self, $client, $user_session, $uid, $setting ) = @_; my( $staff, $user, $evt ) = $apputils->checkses_requestor( $user_session, $uid, 'VIEW_USER' ); @@ -128,7 +128,10 @@ sub user_settings { 'open-ils.cstore', 'open-ils.cstore.direct.actor.user_setting.search.atomic', { usr => $uid } ); - return { map { ( $_->name => JSON->JSON2perl($_->value) ) } @$s }; + my $settings = { map { ( $_->name => JSON->JSON2perl($_->value) ) } @$s }; + + return $$settings{$setting} if $setting; + return $settings; } @@ -149,6 +152,28 @@ sub ou_settings { return { map { ( $_->name => JSON->JSON2perl($_->value) ) } @$s }; } + + +__PACKAGE__->register_method( + api_name => 'open-ils.actor.ou_setting.ancestor_default', + method => 'ou_ancestor_setting', +); + +# ------------------------------------------------------------------ +# Attempts to find the org setting value for a given org. if not +# found at the requested org, searches up the org tree until it +# finds a parent that has the requested setting. +# when found, returns { org => $id, value => $value } +# otherwise, returns NULL +# ------------------------------------------------------------------ +sub ou_ancestor_setting { + my( $self, $client, $orgid, $name ) = @_; + return $U->ou_ancestor_setting($orgid, $name); +} + + + + __PACKAGE__->register_method ( method => "ou_setting_delete", api_name => 'open-ils.actor.org_setting.delete', @@ -183,6 +208,14 @@ sub ou_setting_delete { + + + + + + + + __PACKAGE__->register_method( method => "update_patron", api_name => "open-ils.actor.patron.update",); @@ -204,7 +237,6 @@ sub update_patron { return $evt if $evt; - # XXX does this user have permission to add/create users. Granularity? # $new_patron is the patron in progress. $patron is the original patron # passed in with the method. new_patron will change as the components # of patron are added/updated. @@ -419,6 +451,9 @@ sub _update_patron { $patron->clear_ident_value; } + $evt = verify_last_xact($session, $patron); + return (undef, $evt) if $evt; + my $stat = $session->request( "open-ils.storage.direct.actor.user.update",$patron )->gather(1); return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat); @@ -426,6 +461,20 @@ sub _update_patron { return ($patron); } +sub verify_last_xact { + my( $session, $patron ) = @_; + return undef unless $patron->id and $patron->id > 0; + my $p = $session->request( + 'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1); + my $xact = $p->last_xact_id; + return undef unless $xact; + $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id); + return OpenILS::Event->new('XACT_COLLISION') + if $xact != $patron->last_xact_id; + return undef; +} + + sub _check_dup_ident { my( $session, $patron ) = @_; @@ -815,12 +864,7 @@ __PACKAGE__->register_method( sub search_username { my($self, $client, $username) = @_; - my $users = OpenILS::Application::AppUtils->simple_scalar_request( - "open-ils.cstore", - "open-ils.cstore.direct.actor.user.search.atomic", - { usrname => $username } - ); - return $users; + return new_editor()->search_actor_user({usrname=>$username}); } @@ -975,8 +1019,8 @@ sub build_org_tree { my( $self, $orglist) = @_; - return $orglist unless ( - ref($orglist) and @$orglist > 1 ); + return $orglist unless ref $orglist; + return $$orglist[0] if @$orglist == 1; my @list = sort { $a->ou_type <=> $b->ou_type || @@ -1053,9 +1097,10 @@ __PACKAGE__->register_method( ); sub get_my_org_path { - my( $self, $client, $user_session, $org_id ) = @_; - my $user_obj = $apputils->check_user_session($user_session); - if(!defined($org_id)) { $org_id = $user_obj->home_ou; } + my( $self, $client, $auth, $org_id ) = @_; + my $e = new_editor(authtoken=>$auth); + return $e->event unless $e->checkauth; + $org_id = $e->requestor->ws_ou unless defined $org_id; return $apputils->simple_scalar_request( "open-ils.storage", @@ -1079,6 +1124,7 @@ sub patron_adv_search { +=head old sub _verify_password { my($user_session, $password) = @_; my $user_obj = $apputils->check_user_session($user_session); @@ -1114,8 +1160,13 @@ sub update_password { my $evt; + my $session = $apputils->start_db_session(); my $user_obj = $apputils->check_user_session($user_session); + #fetch the in-database version so we get the latest xact_id + $user_obj = $session->request( + 'open-ils.storage.direct.actor.user.retrieve', $user_obj->id)->gather(1); + if($self->api_name =~ /password/o) { #make sure they know the current password @@ -1138,7 +1189,6 @@ sub update_password { $user_obj->email($new_value); } - my $session = $apputils->start_db_session(); ( $user_obj, $evt ) = _update_patron($session, $user_obj, $user_obj, 1); return $evt if $evt; @@ -1148,6 +1198,60 @@ sub update_password { if($user_obj) { return 1; } return undef; } +=cut + +__PACKAGE__->register_method( + method => "update_passwd", + api_name => "open-ils.actor.user.password.update"); + +__PACKAGE__->register_method( + method => "update_passwd", + api_name => "open-ils.actor.user.username.update"); + +__PACKAGE__->register_method( + method => "update_passwd", + api_name => "open-ils.actor.user.email.update"); + +sub update_passwd { + my( $self, $conn, $auth, $new_val, $orig_pw ) = @_; + my $e = new_editor(xact=>1, authtoken=>$auth); + return $e->die_event unless $e->checkauth; + + my $db_user = $e->retrieve_actor_user($e->requestor->id) + or return $e->die_event; + my $api = $self->api_name; + + if( $api =~ /password/o ) { + + # make sure the original password matches the in-database password + return OpenILS::Event->new('INCORRECT_PASSWORD') + if md5_hex($orig_pw) ne $db_user->passwd; + $db_user->passwd($new_val); + + } else { + + # if we don't clear the password, the user will be updated with + # a hashed version of the hashed version of their password + $db_user->clear_passwd; + + if( $api =~ /username/o ) { + + # make sure no one else has this username + my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); + return OpenILS::Event->new('USERNAME_EXISTS') if @$exist; + $db_user->usrname($new_val); + + } elsif( $api =~ /email/o ) { + $db_user->email($new_val); + } + } + + $e->update_actor_user($db_user) or return $e->die_event; + $e->commit; + return 1; +} + + __PACKAGE__->register_method( @@ -1533,6 +1637,8 @@ __PACKAGE__->register_method( sub user_transaction_retrieve { my( $self, $client, $login_session, $bill_id ) = @_; + # XXX I think I'm deprecated... make sure + my $trans = $apputils->simple_scalar_request( "open-ils.cstore", "open-ils.cstore.direct.money.billable_transaction_summary.retrieve", @@ -2053,7 +2159,8 @@ sub user_transaction_history { flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] }, order_by => { mbt => 'xact_start DESC' }, } - ] + ], + {substream => 1} ) }; $e->rollback; @@ -2575,6 +2682,56 @@ sub session_home_lib { return $org->shortname; } +__PACKAGE__->register_method( + method => 'session_safe_token', + api_name => 'open-ils.actor.session.safe_token', + signature => q/ + Returns a hashed session ID that is safe for export to the world. + This safe token will expire after 1 hour of non-use. + @param auth Active authentication token + / +); + +sub session_safe_token { + my( $self, $conn, $auth ) = @_; + my $e = new_editor(authtoken=>$auth); + return undef unless $e->checkauth; + + my $safe_token = md5_hex($auth); + + $cache ||= OpenSRF::Utils::Cache->new("global", 0); + + # Add more like the following if needed... + $cache->put_cache( + "safe-token-home_lib-shortname-$safe_token", + $e->retrieve_actor_org_unit( + $e->requestor->home_ou + )->shortname, + 60 * 60 + ); + + return $safe_token; +} + + +__PACKAGE__->register_method( + method => 'safe_token_home_lib', + api_name => 'open-ils.actor.safe_token.home_lib.shortname', + signature => q/ + Returns the home library shortname from the session + asscociated with a safe token from generated by + open-ils.actor.session.safe_token. + @param safe_token Active safe token + / +); + +sub safe_token_home_lib { + my( $self, $conn, $safe_token ) = @_; + + $cache ||= OpenSRF::Utils::Cache->new("global", 0); + return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token ); +} + __PACKAGE__->register_method( @@ -2721,5 +2878,8 @@ sub user_retrieve_parts { + + + 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm b/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm index 334e6b6531..6f5412f5a6 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Actor/Container.pm @@ -194,80 +194,37 @@ sub bucket_create { $bucket->clear_id; - $logger->debug("creating bucket: " . Dumper($bucket)); + my $evt = OpenILS::Event->new('CONTAINER_EXISTS', + payload => [$class, $bucket->owner, $bucket->btype, $bucket->name]); + my $search = {name => $bucket->name, owner => $bucket->owner, btype => $bucket->btype}; - my $stat; + my $obj; if( $class eq 'copy' ) { + return $evt if $e->search_container_copy_bucket($search)->[0]; return $e->event unless - $stat = $e->create_container_copy_bucket($bucket); + $obj = $e->create_container_copy_bucket($bucket); } if( $class eq 'callnumber' ) { + return $evt if $e->search_container_call_number_bucket($search)->[0]; return $e->event unless - $stat = $e->create_container_call_number_bucket($bucket); + $obj = $e->create_container_call_number_bucket($bucket); } if( $class eq 'biblio' ) { + return $evt if $e->search_container_biblio_record_entry_bucket($search)->[0]; return $e->event unless - $stat = $e->create_container_biblio_record_entry_bucket($bucket); + $obj = $e->create_container_biblio_record_entry_bucket($bucket); } if( $class eq 'user') { + return $evt if $e->search_container_user_bucket($search)->[0]; return $e->event unless - $stat = $e->create_container_user_bucket($bucket); + $obj = $e->create_container_user_bucket($bucket); } $e->commit; - return $stat->id; -} - - -__PACKAGE__->register_method( - method => "bucket_delete", - api_name => "open-ils.actor.container.delete", - notes => <<" NOTES"); - Deletes a bucket object. If requestor is different from - bucketOwner, requestor needs DELETE_CONTAINER permissions - PARAMS(authtoken, class, bucketId); - Returns the new bucket object - NOTES - -sub bucket_delete { - my( $self, $client, $authtoken, $class, $bucketid ) = @_; - my( $bucket, $evt ); - - my $e = new_editor(xact=>1, authtoken=>$authtoken); - return $e->event unless $e->checkauth; - - ( $bucket, $evt ) = $U->fetch_container_e($e, $bucketid, $class); - return $evt if $evt; - - return $e->event unless $e->allowed('DELETE_CONTAINER'); - - my $stat; - if( $class eq 'copy' ) { - return $e->event unless - $stat = $e->delete_container_copy_bucket($bucket); - } - - if( $class eq 'callnumber' ) { - return $e->event unless - $stat = $e->delete_container_call_number_bucket($bucket); - } - - if( $class eq 'biblio' ) { - return $e->event unless - $stat = $e->delete_container_biblio_record_entry_bucket($bucket); - } - - if( $class eq 'user') { - return $e->event unless - $stat = $e->delete_container_user_bucket($bucket); - } - - $e->commit; - return $stat; - + return $obj->id; } @@ -354,7 +311,9 @@ sub __item_delete { return $evt if $evt; if( $bucket->owner ne $e->requestor->id ) { - return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM'); + my $owner = $e->retrieve_actor_user($bucket->owner) + or return $e->die_event; + return $e->event unless $e->allowed('DELETE_CONTAINER_ITEM', $owner->home_ou); } my $stat; @@ -399,7 +358,9 @@ sub full_delete { return $evt if $evt; if( $container->owner ne $e->requestor->id ) { - return $e->event unless $e->allowed('DELETE_CONTAINER'); + my $owner = $e->retrieve_actor_user($container->owner) + or return $e->die_event; + return $e->event unless $e->allowed('DELETE_CONTAINER', $owner->home_ou); } my $items; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm index 3685452241..0ead768620 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm @@ -246,8 +246,8 @@ sub build_org_tree { my( $self, $orglist, $add_types ) = @_; - return $orglist unless ( - ref($orglist) and @$orglist > 1 ); + return $orglist unless ref $orglist; + return $$orglist[0] if @$orglist == 1; my @list = sort { $a->ou_type <=> $b->ou_type || @@ -270,7 +270,6 @@ sub build_org_tree { } return $list[0]; - } sub fetch_closed_date { @@ -437,6 +436,7 @@ sub record_to_mvr { $u->start_mods_batch( $record->marc ); my $mods = $u->finish_mods_batch(); $mods->doc_id($record->id); + $mods->tcn($record->tcn_value); return $mods; } @@ -1131,7 +1131,7 @@ sub patron_total_items_out { sub fetch_mbts { my $self = shift; my $id = shift; - my $editor = shift || new_editor(); + my $editor = shift || OpenILS::Utils::CStoreEditor->new; $id = $id->id if (ref($id)); @@ -1198,8 +1198,8 @@ sub make_mbts { $s->total_paid( sprintf('%0.2f', $tp / 100 ) ); $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) ); - $s->xact_type( 'grocery' ) if ($x->grocery); - $s->xact_type( 'circulation' ) if ($x->circulation); + $s->xact_type('grocery') if ($x->grocery); + $s->xact_type('circulation') if ($x->circulation); $logger->debug("Created mbts with balance_owed = ". $s->balance_owed); @@ -1210,9 +1210,42 @@ sub make_mbts { } +sub ou_ancestor_setting_value { + my $obj = ou_ancestor_setting(@_); + return ($obj) ? $obj->{value} : undef; +} + +sub ou_ancestor_setting { + my( $self, $orgid, $name, $e ) = @_; + $e = $e || OpenILS::Utils::CStoreEditor->new; + + do { + my $setting = $e->search_actor_org_unit_setting({org_unit=>$orgid, name=>$name})->[0]; + + if( $setting ) { + $logger->info("found org_setting $name at org $orgid : " . $setting->value); + return { org => $orgid, value => JSON->JSON2perl($setting->value) }; + } + + my $org = $e->retrieve_actor_org_unit($orgid) or return $e->event; + $orgid = $org->parent_ou or return undef; + + } while(1); + + return undef; +} - - + +# returns the ISO8601 string representation of the requested epoch in GMT +sub epoch2ISO8601 { + my( $self, $epoch ) = @_; + my ($sec,$min,$hour,$mday,$mon,$year) = gmtime($epoch); + $year += 1900; $mon += 1; + my $date = sprintf( + '%s-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d-00', + $year, $mon, $mday, $hour, $min, $sec); + return $date; +} 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm b/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm index 30f9de0f08..eb521ded3d 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Cat.pm @@ -56,21 +56,40 @@ __PACKAGE__->register_method( sub retrieve_marc_template { my( $self, $client, $type ) = @_; - return $marctemplates{$type} if defined($marctemplates{$type}); $marctemplates{$type} = _load_marc_template($type); return $marctemplates{$type}; } -sub _load_marc_template { - my $type = shift; +__PACKAGE__->register_method( + method => 'fetch_marc_template_types', + api_name => 'open-ils.cat.marc_template.types.retrieve' +); + +my $marc_template_files; + +sub fetch_marc_template_types { + my( $self, $conn ) = @_; + __load_marc_templates(); + return [ keys %$marc_template_files ]; +} +sub __load_marc_templates { + return if $marc_template_files; if(!$conf) { $conf = OpenSRF::Utils::SettingsClient->new; } - my $template = $conf->config_value( - "apps", "open-ils.cat","app_settings", "marctemplates", $type ); - warn "Opening template file $template\n"; + $marc_template_files = $conf->config_value( + "apps", "open-ils.cat","app_settings", "marctemplates" ); + $logger->info("Loaded marc templates: " . Dumper($marc_template_files)); +} + +sub _load_marc_template { + my $type = shift; + + __load_marc_templates(); + + my $template = $$marc_template_files{$type}; open( F, $template ) or throw OpenSRF::EX::ERROR ("Unable to open MARC template file: $template : $@"); @@ -86,8 +105,7 @@ sub bib_source_from_name { my $name = shift; $logger->debug("searching for bib source: $name"); - $__bib_sources = new_editor()->retrieve_all_config_bib_source() - unless $__bib_sources; + fetch_bib_sources(); my ($s) = grep { lc($_->source) eq lc($name) } @$__bib_sources; @@ -96,6 +114,17 @@ sub bib_source_from_name { } +__PACKAGE__->register_method( + method => 'fetch_bib_sources', + api_name => 'open-ils.cat.bib_sources.retrieve.all'); + +sub fetch_bib_sources { + $__bib_sources = new_editor()->retrieve_all_config_bib_source() + unless $__bib_sources; + return $__bib_sources; +} + + __PACKAGE__->register_method( method => "create_record_xml", @@ -162,13 +191,16 @@ __PACKAGE__->register_method( sub biblio_record_replace_marc { my( $self, $conn, $auth, $recid, $newxml, $source ) = @_; - my $e = OpenILS::Utils::Editor->new(authtoken=>$auth, xact=>1); + warn "Updating MARC with xml\n$newxml\n"; - return $e->event unless $e->checkauth; - return $e->event unless $e->allowed('CREATE_MARC'); + #my $e = OpenILS::Utils::Editor->new(authtoken=>$auth, xact=>1); + my $e = new_editor(authtoken=>$auth, xact=>1); + + return $e->die_event unless $e->checkauth; + return $e->die_event unless $e->allowed('CREATE_MARC'); my $rec = $e->retrieve_biblio_record_entry($recid) - or return $e->event; + or return $e->die_event; my $fixtcn = 1 if $self->api_name =~ /replace/o; @@ -176,10 +208,14 @@ sub biblio_record_replace_marc { # If we're not updating the TCN, all we care about it the marcdoc my $override = $self->api_name =~ /override/; + my $storage = OpenSRF::AppSession->create('open-ils.storage'); + # XXX should .update even bother with the tcn_info if it's not going to replace it? + # there is the potential for returning a TCN_EXISTS event, even though no replacement happens + my( $tcn, $tsource, $marcdoc, $evt) = - _find_tcn_info($e->session, $newxml, $override, $recid); + _find_tcn_info($storage, $newxml, $override, $recid); return $evt if $evt; @@ -229,7 +265,7 @@ __PACKAGE__->register_method( sub biblio_record_xml_import { - my( $self, $client, $authtoken, $xml, $source) = @_; + my( $self, $client, $authtoken, $xml, $source, $auto_tcn) = @_; my $override = 1 if $self->api_name =~ /override/; @@ -239,10 +275,15 @@ sub biblio_record_xml_import { my $session = $apputils->start_db_session(); - ( $tcn, $tcn_source, $marcdoc, $evt ) = _find_tcn_info($session, $xml, $override); - return $evt if $evt; + if( $auto_tcn ) { + # auto_tcn forces a blank TCN value so the DB will have to generate one for us + $marcdoc = __make_marc_doc($xml); + } else { + ( $tcn, $tcn_source, $marcdoc, $evt ) = _find_tcn_info($session, $xml, $override); + return $evt if $evt; + } - $logger->activity("user ".$requestor->id. + $logger->info("user ".$requestor->id. " creating new biblio entry with tcn=$tcn and tcn_source $tcn_source"); my $record = Fieldmapper::biblio::record_entry->new; @@ -277,6 +318,14 @@ sub biblio_record_xml_import { return undef; } +sub __make_marc_doc { + my $xml = shift; + my $marcxml = XML::LibXML->new->parse_string( $xml ); + $marcxml->documentElement->setNamespace( + "http://www.loc.gov/MARC21/slim", "marc", 1 ); + return $marcxml; +} + sub _find_tcn_info { my $session = shift; @@ -285,9 +334,10 @@ sub _find_tcn_info { my $existing_rec = shift || 0; # parse the XML - my $marcxml = XML::LibXML->new->parse_string( $xml ); - $marcxml->documentElement->setNamespace( - "http://www.loc.gov/MARC21/slim", "marc", 1 ); + my $marcxml = __make_marc_doc($xml); +# my $marcxml = XML::LibXML->new->parse_string( $xml ); +# $marcxml->documentElement->setNamespace( +# "http://www.loc.gov/MARC21/slim", "marc", 1 ); my $xpath = '//marc:controlfield[@tag="001"]'; my $tcn = $marcxml->documentElement->findvalue($xpath); @@ -304,7 +354,9 @@ sub _find_tcn_info { # if we're overriding, try to find a different TCN to use if( $override ) { - $logger->activity("tcn value $tcn already exists, attempting to override"); + # XXX Create ALLOW_ALT_TCN permission check support + + $logger->info("tcn value $tcn already exists, attempting to override"); if(!$tcn) { return ( @@ -449,145 +501,6 @@ sub _tcn_exists { } - - -# XXX deprecated. Remove me. - -=head deprecated - -__PACKAGE__->register_method( - method => "biblio_record_tree_retrieve", - api_name => "open-ils.cat.biblio.record.tree.retrieve", -); - -sub biblio_record_tree_retrieve { - - my( $self, $client, $recordid ) = @_; - - my $name = "open-ils.storage.direct.biblio.record_entry.retrieve"; - my $session = OpenSRF::AppSession->create( "open-ils.storage" ); - my $request = $session->request( $name, $recordid ); - my $marcxml = $request->gather(1); - - if(!$marcxml) { - throw OpenSRF::EX::ERROR - ("No record in database with id $recordid"); - } - - $session->disconnect(); - $session->kill_me(); - - warn "turning into nodeset\n"; - my $nodes = OpenILS::Utils::FlatXML->new()->xml_to_nodeset( $marcxml->marc ); - warn "turning nodeset into tree\n"; - my $tree = $utils->nodeset2tree( $nodes->nodeset ); - - $tree->owner_doc( $marcxml->id() ); - - warn "returning tree\n"; - - return $tree; -} -=cut - - -=head deprecate -__PACKAGE__->register_method( - method => "biblio_record_xml_update", - api_name => "open-ils.cat.biblio.record.xml.update", - argc => 3, #(session_id, biblio_tree ) - notes => <<' NOTES'); - Updates the XML of a biblio record entry - @param authtoken The session token for the staff updating the record - @param docID The record entry ID to update - @param xml The new MARCXML record - NOTES - -sub biblio_record_xml_update { - - my( $self, $client, $user_session, $id, $xml ) = @_; - - my $user_obj = $apputils->check_user_session($user_session); - - if($apputils->check_user_perms( - $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) { - return OpenILS::Perm->new("UPDATE_MARC"); - } - - $logger->activity("user ".$user_obj->id." updating biblio record $id"); - - - my $session = OpenILS::Application::AppUtils->start_db_session(); - - warn "Retrieving biblio record from storage for update\n"; - - my $req1 = $session->request( - "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $id ); - my $biblio = $req1->gather(1); - - warn "retrieved doc $id\n"; - - my $doc = XML::LibXML->new->parse_string($xml); - throw OpenSRF::EX::ERROR ("Invalid XML in record update: $xml") unless $doc; - - $biblio->marc( entityize( $doc->documentElement->toString ) ); - $biblio->editor( $user_obj->id ); - $biblio->edit_date( 'now' ); - - warn "Sending updated doc $id to db with xml ".$biblio->marc. "\n"; - - my $req = $session->request( - "open-ils.storage.direct.biblio.record_entry.update", $biblio ); - - $req->wait_complete; - my $status = $req->recv(); - if( !$status || $status->isa("Error") || ! $status->content) { - OpenILS::Application::AppUtils->rollback_db_session($session); - if($status->isa("Error")) { throw $status ($status); } - throw OpenSRF::EX::ERROR ("Error updating biblio record"); - } - $req->finish(); - - # Send the doc to the wormer for wormizing - warn "Starting worm session\n"; - - my $success = 0; - my $wresp; - - my $wreq = $session->request( "open-ils.worm.wormize.biblio", $id ); - - my $w = 0; - try { - $w = $wreq->gather(1); - - } catch Error with { - my $e = shift; - warn "wormizing failed, rolling back\n"; - OpenILS::Application::AppUtils->rollback_db_session($session); - - if($e) { throw $e ($e); } - throw OpenSRF::EX::ERROR ("Wormizing Failed for $id" ); - }; - - warn "Committing db session...\n"; - OpenILS::Application::AppUtils->commit_db_session( $session ); - -# $client->respond_complete($tree); - - warn "Done wormizing\n"; - - #use Data::Dumper; - #warn "Returning tree:\n"; - #warn Dumper $tree; - - return $biblio; - -} - -=cut - - - __PACKAGE__->register_method( method => "biblio_record_record_metadata", api_name => "open-ils.cat.biblio.record.metadata.retrieve", @@ -1121,15 +1034,8 @@ sub remove_empty_objects { $editor->update_asset_call_number($vol) or return $editor->event; } - # then delete the record this volume points to - my $rec = $editor->retrieve_biblio_record_entry($vol->record) - or return $editor->event; - - unless( $U->is_true($rec->deleted) ) { - $rec->deleted('t'); - $rec->active('f'); - $editor->update_biblio_record_entry($rec) or return $editor->event; - } + my $evt = delete_rec($editor, $vol->record); + return $evt if $evt; } else { return OpenILS::Event->new('TITLE_LAST_COPY', payload => $vol->record ); @@ -1139,10 +1045,31 @@ sub remove_empty_objects { return undef; } +# marks a record as deleted +sub delete_rec { + my( $editor, $rec_id ) = @_; + + my $rec = $editor->retrieve_biblio_record_entry($rec_id) + or return $editor->event; + + return undef if $U->is_true($rec->deleted); + + $rec->deleted('t'); + $rec->active('f'); + $rec->editor( $editor->requestor->id ); + $rec->edit_date('now'); + $editor->update_biblio_record_entry($rec) or return $editor->event; + + return undef; +} + sub delete_copy { my( $editor, $override, $vol, $copy ) = @_; + return $editor->event unless + $editor->allowed('DELETE_COPY',copy_perm_org($vol, $copy)); + my $stat = $U->copy_status($copy->status)->id; unless($override) { @@ -1181,6 +1108,12 @@ sub create_copy { return OpenILS::Event->new('ITEM_BARCODE_EXISTS') if @$existing; + # see if the volume this copy references is marked as deleted + my $evol = $editor->retrieve_asset_call_number($copy->call_number) + or return $editor->event; + return OpenILS::Event->new('VOLUME_DELETED', vol => $evol->id) + if $U->is_true($evol->deleted); + my $evt; my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib; return $evt if ( $evt = org_cannot_have_vols($editor, $org) ); @@ -1260,6 +1193,12 @@ sub create_volume { return $evt if ( $evt = org_cannot_have_vols($editor, $vol->owning_lib) ); + # see if the record this volume references is marked as deleted + my $rec = $editor->retrieve_biblio_record_entry($vol->record) + or return $editor->event; + return OpenILS::Event->new('BIB_RECORD_DELETED', rec => $rec->id) + if $U->is_true($rec->deleted); + # first lets see if there are any collisions my $vols = $editor->search_asset_call_number( { owning_lib => $vol->owning_lib, @@ -1271,6 +1210,7 @@ sub create_volume { my $label = undef; if(@$vols) { + # we've found an exising volume if($override) { $label = $vol->label; } else { @@ -1279,8 +1219,9 @@ sub create_volume { } } - # create a temp label so we can create the volume, then de-dup it - $vol->label( '__SYSTEM_TMP_'.time) if $label; + # create a temp label so we can create the new volume, + # then de-dup it with the existing volume + $vol->label( "__SYSTEM_TMP_$$".time) if $label; $vol->creator($editor->requestor->id); $vol->create_date('now'); @@ -1340,6 +1281,8 @@ sub batch_volume_transfer { my $vols = $e->batch_retrieve_asset_call_number($vol_ids); my @seen; + my @rec_ids; + for my $vol (@$vols) { # if we've already looked at this volume, go to the next @@ -1351,6 +1294,7 @@ sub batch_volume_transfer { # take note of the fact that we've looked at this set of volumes push( @seen, $_->id ) for @all; + push( @rec_ids, $_->record ) for @all; # for each volume, see if there are any copies that have a # remote circ_lib (circ_lib != vol->owning_lib and != $o_lib ). @@ -1435,10 +1379,16 @@ sub batch_volume_transfer { } # Now see if any empty records need to be deleted after all of this - for(@all) { - $evt = remove_empty_objects($e, $override, $_); + + for(@rec_ids) { + $logger->debug("merge: seeing if we should delete record $_..."); + $evt = delete_rec($e, $_) if title_is_empty($e, $_); return $evt if $evt; - } + } + + #for(@all) { + # $evt = remove_empty_objects($e, $override, $_); + #} } $logger->info("merge: transfer succeeded"); @@ -1495,12 +1445,16 @@ sub find_or_create_volume { $vol->owning_lib($org_id); $vol->label($label); $vol->record($record_id); - $vol->creator($e->requestor->id); - $vol->create_date('now'); - $vol->editor($e->requestor->id); - $vol->edit_date('now'); - $e->create_asset_call_number($vol) or return $e->die_event; + #$vol->creator($e->requestor->id); + #$vol->create_date('now'); + #$vol->editor($e->requestor->id); + #$vol->edit_date('now'); + #$e->create_asset_call_number($vol) or return $e->die_event; + + my $evt = create_volume( 0, $e, $vol ); + return $evt if $evt; + $e->commit; return $vol->id; } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm index 362147d694..c57f9a6ff6 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ.pm @@ -214,195 +214,234 @@ sub title_from_transaction { } -__PACKAGE__->register_method( - method => "set_circ_lost", - api_name => "open-ils.circ.circulation.set_lost", - NOTES => <<" NOTES"); - Params are login, barcode - login must have SET_CIRC_LOST perms - Sets a circulation to lost - NOTES __PACKAGE__->register_method( - method => "set_circ_lost", - api_name => "open-ils.circ.circulation.set_claims_returned", - NOTES => <<" NOTES"); - Params are login, barcode - login must have SET_CIRC_MISSING perms - Sets a circulation to lost - NOTES - -sub set_circ_lost { - my( $self, $client, $login, $args ) = @_; - my( $user, $circ, $copy, $evt ); - - my $barcode = $$args{barcode}; - my $backdate = $$args{backdate}; - - ( $user, $evt ) = $U->checkses($login); - return $evt if $evt; - - # Grab the related copy - ($copy, $evt) = $U->fetch_copy_by_barcode($barcode); - return $evt if $evt; - - my $isclaims = $self->api_name =~ /claims_returned/; - my $islost = $self->api_name =~ /lost/; - my $session = $U->start_db_session(); - - # grab the circulation -# ( $circ ) = $U->fetch_open_circulation( $copy->id ); -# return 1 unless $circ; + method => "new_set_circ_lost", + api_name => "open-ils.circ.circulation.set_lost", + signature => q/ + Sets the copy and related open circulation to lost + @param auth + @param args : barcode + / +); - $circ = new_editor()->search_action_circulation( - { checkin_time => undef, target_copy => $copy->id } )->[0]; - return 1 unless $circ; - if($islost) { - $evt = _set_circ_lost($copy, $circ, $user, $session) if $islost; - return $evt if $evt; +# --------------------------------------------------------------------- +# Sets a circulation to lost. updates copy status to lost +# applies copy and/or prcoessing fees depending on org settings +# --------------------------------------------------------------------- +sub new_set_circ_lost { + my( $self, $conn, $auth, $args ) = @_; + + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + + my $barcode = $$args{barcode}; + $logger->info("marking item lost $barcode"); + + # --------------------------------------------------------------------- + # gather the pieces + my $copy = $e->search_asset_copy([ + {barcode=>$barcode, deleted=>'f'}, + {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])->[0] + or return $e->die_event; + + my $owning_lib = + ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ? + $copy->circ_lib : $copy->call_number->owning_lib; + + my $circ = $e->search_action_circulation( + {checkin_time => undef, target_copy => $copy->id} )->[0] + or return $e->die_event; + + $e->allowed('SET_CIRC_LOST', $circ->circ_lib) or return $e->die_event; + + # --------------------------------------------------------------------- + # fetch the related org settings + my $default_price = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_DEF_ITEM_PRICE, $e) || 0; + my $proc_fee = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_LOST_PROCESSING_FEE, $e) || 0; + my $charge_on_0 = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_CHARGE_LOST_ON_ZERO, $e) || 0; + my $void_overdue = $U->ou_ancestor_setting_value( + $owning_lib, OILS_SETTING_VOID_OVERDUE_ON_LOST, $e) || 0; + + $logger->info("org settings: default price = $default_price, ". + "processing fee = $proc_fee, charge on 0 = $charge_on_0, void overdues = $void_overdue"); + + # --------------------------------------------------------------------- + # move the copy into LOST status + unless( $copy->status == OILS_COPY_STATUS_LOST ) { + $copy->status(OILS_COPY_STATUS_LOST); + $copy->editor($e->requestor->id); + $copy->edit_date('now'); + $e->update_asset_copy($copy) or return $e->die_event; } - if($isclaims) { - $evt = _set_circ_claims_returned( - $user, $circ, $session, $backdate ); - return $evt if $evt; + # --------------------------------------------------------------------- + # determine the appropriate item price to charge and create the billing + my $price = $copy->price; + $price = $default_price unless defined $price; + $price = 0 if $price < 0; + $price = $default_price if $price == 0 and $charge_on_0; + + if( $price > 0 ) { + my $evt = create_bill($e, $price, 'Lost Materials', $circ->id); + return $evt if $evt; + } + + # --------------------------------------------------------------------- + # if there is a processing fee, charge that too + if( $proc_fee > 0 ) { + my $evt = create_bill($e, $proc_fee, 'Lost Materials Processing Fee', $circ->id); + return $evt if $evt; + } + + # --------------------------------------------------------------------- + # mark the circ as lost and stop the fines + $circ->stop_fines(OILS_STOP_FINES_LOST); + $circ->stop_fines_time('now') unless $circ->stop_fines_time; + $e->update_action_circulation($circ) or return $e->die_event; + + # --------------------------------------------------------------------- + # void all overdue fines on this circ if configured + if( $void_overdue ) { + my $evt = void_overdues($e, $circ); + return $evt if $evt; + } + + $e->commit; + return 1; +} - } - $circ->stop_fines_time('now') unless $circ->stop_fines_time; - my $s = $session->request( - "open-ils.storage.direct.action.circulation.update", $circ )->gather(1); +sub create_bill { + my( $e, $amount, $type, $xactid ) = @_; - return $U->DB_UPDATE_FAILED($circ) unless defined($s); - $U->commit_db_session($session); + $logger->info("The system is charging $amount [$type] on xact $xactid"); - return 1; -} + # ----------------------------------------------------------------- + # make sure the transaction is not closed + my $xact = $e->retrieve_money_billable_transaction($xactid) + or return $e->die_event; -sub _set_circ_lost { - my( $copy, $circ, $reqr, $session ) = @_; + if( $xact->xact_finish ) { + my ($mbts) = $U->fetch_mbts($xactid, $e); + if( ($mbts->balance_owed + $amount) != 0 ) { + $logger->info("* re-opening xact $xactid"); + $xact->clear_xact_finish; + $e->update_money_billable_transaction($xact) + or return $e->die_event; + } + } - my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_LOST'); - return $evt if $evt; + # ----------------------------------------------------------------- + # now create the billing + my $bill = Fieldmapper::money::billing->new; + $bill->xact($xactid); + $bill->amount($amount); + $bill->billing_type($type); + $bill->note('SYSTEM GENERATED'); + $e->create_money_billing($bill) or return $e->die_event; - $logger->activity("user ".$reqr->id." marking copy ".$copy->id. - " lost for circ ". $circ->id. " and checking for necessary charges"); + return undef; +} - if( $copy->status ne OILS_COPY_STATUS_LOST ) { - $copy->status(OILS_COPY_STATUS_LOST); - $U->update_copy( - copy => $copy, - editor => $reqr->id, - session => $session); - } - # if the copy has a price defined and/or a processing fee, bill the patron - - # if the location that owns the copy has a processing fee, charge the user - my $owner = $U->fetch_copy_owner($copy->id); - $logger->info("circ fetching org settings for $owner ". - "to determine processing fee and default copy price"); - - my $settings = $U->simplereq( - 'open-ils.actor', 'open-ils.actor.org_unit.settings.retrieve', $owner ); - my $fee = $settings->{'circ.lost_materials_processing_fee'} || 0; - - # If the copy has a price configured, charge said price to the user - # otherwise use the default price - my $s = OILS_SETTING_DEF_ITEM_PRICE; - my $copy_price = $copy->price; - $copy_price = $settings->{$s} unless defined $copy_price; - if($copy_price and $copy_price > 0) { - $logger->debug("lost copy has a price of $copy_price"); - $evt = _make_bill($session, $copy_price, 'Lost Materials', $circ->id); - return $evt if $evt; - } +# ----------------------------------------------------------------- +# Voids overdue fines on the given circ. if a backdate is +# provided, then we only void back to the backdate +# ----------------------------------------------------------------- +sub void_overdues { + my( $e, $circ, $backdate ) = @_; + + my $bill_search = { + xact => $circ->id, + billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS + }; + + if( $backdate ) { + # ------------------------------------------------------------------ + # Fines for overdue materials are assessed up to, but not including, + # one fine interval after the fines are applicable. Here, we add + # one fine interval to the backdate to ensure that we are not + # voiding fines that were applicable before the backdate. + # ------------------------------------------------------------------ + my $interval = OpenSRF::Utils->interval_to_seconds($circ->fine_interval); + my $date = DateTime::Format::ISO8601->parse_datetime($backdate); + $backdate = $U->epoch2ISO8601($date->epoch + $interval); + $logger->info("applying backdate $backdate in overdue voiding"); + $$bill_search{billing_ts} = {'>=' => $backdate}; + } + + my $bills = $e->search_money_billing($bill_search); + + for my $bill (@$bills) { + next if $U->is_true($bill->voided); + $logger->info("voiding overdue bill ".$bill->id); + $bill->voided('t'); + $bill->void_time('now'); + $bill->voider($e->requestor->id); + my $n = $bill->note || ""; + $bill->note("$n\nSystem: VOIDED FOR BACKDATE"); + $e->update_money_billing($bill) or return $e->die_event; + } - if( $fee ) { - $evt = _make_bill($session, $fee, 'Lost Materials Processing Fee', $circ->id); - return $evt if $evt; - } - - $circ->stop_fines(OILS_STOP_FINES_LOST); return undef; } -sub _make_bill { - my( $session, $amount, $type, $xactid ) = @_; - $logger->activity("The system is charging $amount ". - " [$type] for lost materials on circulation $xactid"); - my $bill = Fieldmapper::money::billing->new; - $bill->xact($xactid); - $bill->amount($amount); - $bill->billing_type($type); # - XXX these strings should be configurable some day - $bill->note('SYSTEM GENERATED'); +__PACKAGE__->register_method( + method => "set_circ_claims_returned", + api_name => "open-ils.circ.circulation.set_claims_returned", + signature => q/ + Sets the circ for the given item as claims returned + If a backdate is provided, overdue fines will be voided + back to the backdate + @param auth + @param args : barcode, backdate + / +); - my $id = $session->request( - 'open-ils.storage.direct.money.billing.create', $bill )->gather(1); +sub set_circ_claims_returned { + my( $self, $conn, $auth, $args ) = @_; - return $U->DB_UPDATE_FAILED($bill) unless defined $id; - return undef; -} + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; -sub _set_circ_claims_returned { - my( $reqr, $circ, $session, $backdate ) = @_; + my $barcode = $$args{barcode}; + my $backdate = $$args{backdate}; - my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_CLAIMS_RETURNED'); - return $evt if $evt; - $circ->stop_fines("CLAIMSRETURNED"); + $logger->info("marking circ for item $barcode as claims returned". + (($backdate) ? " with backdate $backdate" : '')); - $logger->info("user ".$reqr->id. - " marking circ". $circ->id. " as claims returned with backdate $backdate"); + my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0] + or return $e->die_event; - # allow the caller to backdate the circulation and void any fines - # that occurred after the backdate - if($backdate) { - my $s = cr_handle_backdate($backdate, $circ, $reqr, $session ); - $logger->debug("backdate method returned $s"); - $circ->stop_fines_time(clense_ISO8601($backdate)) - } + my $circ = $e->search_action_circulation( + {checkin_time => undef, target_copy => $copy->id})->[0] + or return $e->die_event; - return undef; -} + $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib) + or return $e->die_event; -sub cr_handle_backdate { - my( $backdate, $circ, $requestor, $session, $closecirc ) = @_; - - my $bd = $backdate; - $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og; - $bd = "${bd}T23:59:59"; - - my $bills = $session->request( - "open-ils.storage.direct.money.billing.search_where.atomic", - billing_ts => { '>=' => $bd }, - xact => $circ->id, - billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS - )->gather(1); - - $logger->debug("backdate found ".scalar(@$bills)." bills to void"); - - if($bills) { - for my $bill (@$bills) { - unless( $U->is_true($bill->voided) ) { - $logger->info("voiding bill ".$bill->id); - $bill->voided('t'); - $bill->void_time('now'); - $bill->voider($requestor->id); - my $n = $bill->note || ""; - $bill->note($n . "\nSystem: VOIDED FOR BACKDATE"); - my $s = $session->request( - "open-ils.storage.direct.money.billing.update", $bill)->gather(1); - return $U->DB_UPDATE_FAILED($bill) unless $s; - } - } - } + $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED); + $circ->stop_fines_time('now') unless $circ->stop_fines_time; + + if( $backdate ) { + # make it look like the circ stopped at the cliams returned time + $circ->stop_fines_time(clense_ISO8601($backdate)); + my $evt = void_overdues($e, $circ, $backdate); + return $evt if $evt; + } - return 100; + $e->update_action_circulation($circ) or return $e->die_event; + $e->commit; + return 1; } @@ -843,8 +882,10 @@ __PACKAGE__->register_method( api_name => 'open-ils.circ.copy_details.retrieve.barcode'); sub copy_details_barcode { my( $self, $conn, $auth, $barcode ) = @_; - return $self->copy_details( $conn, $auth, - new_editor()->search_asset_copy({barcode=>$barcode,deleted=>'f'},{idlist=>1})->[0]); + my $e = new_editor(); + my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0]; + return $e->event unless $cid; + return $self->copy_details( $conn, $auth, $cid ); } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm index 19c34ac52c..ece07496c4 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Circulate.pm @@ -11,252 +11,253 @@ my $script_libs; sub initialize { - my $self = shift; - my $conf = OpenSRF::Utils::SettingsClient->new; - my @pfx2 = ( "apps", "open-ils.circ","app_settings" ); - my @pfx = ( @pfx2, "scripts" ); - - my $p = $conf->config_value( @pfx, 'circ_permit_patron' ); - my $c = $conf->config_value( @pfx, 'circ_permit_copy' ); - my $d = $conf->config_value( @pfx, 'circ_duration' ); - my $f = $conf->config_value( @pfx, 'circ_recurring_fines' ); - my $m = $conf->config_value( @pfx, 'circ_max_fines' ); - my $pr = $conf->config_value( @pfx, 'circ_permit_renew' ); - my $lb = $conf->config_value( @pfx2, 'script_path' ); - - $logger->error( "Missing circ script(s)" ) - unless( $p and $c and $d and $f and $m and $pr ); - - $scripts{circ_permit_patron} = $p; - $scripts{circ_permit_copy} = $c; - $scripts{circ_duration} = $d; - $scripts{circ_recurring_fines}= $f; - $scripts{circ_max_fines} = $m; - $scripts{circ_permit_renew} = $pr; - - $lb = [ $lb ] unless ref($lb); - $script_libs = $lb; - - $logger->debug( - "circulator: Loaded rules scripts for circ: " . - "circ permit patron = $p, ". - "circ permit copy = $c, ". - "circ duration = $d, ". - "circ recurring fines = $f, " . - "circ max fines = $m, ". - "circ renew permit = $pr. ". - "lib paths = @$lb"); + my $self = shift; + my $conf = OpenSRF::Utils::SettingsClient->new; + my @pfx2 = ( "apps", "open-ils.circ","app_settings" ); + my @pfx = ( @pfx2, "scripts" ); + + my $p = $conf->config_value( @pfx, 'circ_permit_patron' ); + my $c = $conf->config_value( @pfx, 'circ_permit_copy' ); + my $d = $conf->config_value( @pfx, 'circ_duration' ); + my $f = $conf->config_value( @pfx, 'circ_recurring_fines' ); + my $m = $conf->config_value( @pfx, 'circ_max_fines' ); + my $pr = $conf->config_value( @pfx, 'circ_permit_renew' ); + my $lb = $conf->config_value( @pfx2, 'script_path' ); + + $logger->error( "Missing circ script(s)" ) + unless( $p and $c and $d and $f and $m and $pr ); + + $scripts{circ_permit_patron} = $p; + $scripts{circ_permit_copy} = $c; + $scripts{circ_duration} = $d; + $scripts{circ_recurring_fines}= $f; + $scripts{circ_max_fines} = $m; + $scripts{circ_permit_renew} = $pr; + + $lb = [ $lb ] unless ref($lb); + $script_libs = $lb; + + $logger->debug( + "circulator: Loaded rules scripts for circ: " . + "circ permit patron = $p, ". + "circ permit copy = $c, ". + "circ duration = $d, ". + "circ recurring fines = $f, " . + "circ max fines = $m, ". + "circ renew permit = $pr. ". + "lib paths = @$lb"); } __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.checkout.permit", - notes => q/ - Determines if the given checkout can occur - @param authtoken The login session key - @param params A trailing hash of named params including - barcode : The copy barcode, - patron : The patron the checkout is occurring for, - renew : true or false - whether or not this is a renewal - @return The event that occurred during the permit check. - /); + method => "run_method", + api_name => "open-ils.circ.checkout.permit", + notes => q/ + Determines if the given checkout can occur + @param authtoken The login session key + @param params A trailing hash of named params including + barcode : The copy barcode, + patron : The patron the checkout is occurring for, + renew : true or false - whether or not this is a renewal + @return The event that occurred during the permit check. + /); __PACKAGE__->register_method ( - method => 'run_method', - api_name => 'open-ils.circ.checkout.permit.override', - signature => q/@see open-ils.circ.checkout.permit/, + method => 'run_method', + api_name => 'open-ils.circ.checkout.permit.override', + signature => q/@see open-ils.circ.checkout.permit/, ); __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.checkout", - notes => q/ - Checks out an item - @param authtoken The login session key - @param params A named hash of params including: - copy The copy object - barcode If no copy is provided, the copy is retrieved via barcode - copyid If no copy or barcode is provide, the copy id will be use - patron The patron's id - noncat True if this is a circulation for a non-cataloted item - noncat_type The non-cataloged type id - noncat_circ_lib The location for the noncat circ. - precat The item has yet to be cataloged - dummy_title The temporary title of the pre-cataloded item - dummy_author The temporary authr of the pre-cataloded item - Default is the home org of the staff member - @return The SUCCESS event on success, any other event depending on the error - /); + method => "run_method", + api_name => "open-ils.circ.checkout", + notes => q/ + Checks out an item + @param authtoken The login session key + @param params A named hash of params including: + copy The copy object + barcode If no copy is provided, the copy is retrieved via barcode + copyid If no copy or barcode is provide, the copy id will be use + patron The patron's id + noncat True if this is a circulation for a non-cataloted item + noncat_type The non-cataloged type id + noncat_circ_lib The location for the noncat circ. + precat The item has yet to be cataloged + dummy_title The temporary title of the pre-cataloded item + dummy_author The temporary authr of the pre-cataloded item + Default is the home org of the staff member + @return The SUCCESS event on success, any other event depending on the error + /); __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.checkin", - argc => 2, - signature => q/ - Generic super-method for handling all copies - @param authtoken The login session key - @param params Hash of named parameters including: - barcode - The copy barcode - force - If true, copies in bad statuses will be checked in and give good statuses - ... - / + method => "run_method", + api_name => "open-ils.circ.checkin", + argc => 2, + signature => q/ + Generic super-method for handling all copies + @param authtoken The login session key + @param params Hash of named parameters including: + barcode - The copy barcode + force - If true, copies in bad statuses will be checked in and give good statuses + ... + / ); __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.checkin.override", - signature => q/@see open-ils.circ.checkin/ + method => "run_method", + api_name => "open-ils.circ.checkin.override", + signature => q/@see open-ils.circ.checkin/ ); __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.renew.override", - signature => q/@see open-ils.circ.renew/, + method => "run_method", + api_name => "open-ils.circ.renew.override", + signature => q/@see open-ils.circ.renew/, ); __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.renew", - notes => <<" NOTES"); - PARAMS( authtoken, circ => circ_id ); - open-ils.circ.renew(login_session, circ_object); - Renews the provided circulation. login_session is the requestor of the - renewal and if the logged in user is not the same as circ->usr, then - the logged in user must have RENEW_CIRC permissions. - NOTES + method => "run_method", + api_name => "open-ils.circ.renew", + notes => <<" NOTES"); + PARAMS( authtoken, circ => circ_id ); + open-ils.circ.renew(login_session, circ_object); + Renews the provided circulation. login_session is the requestor of the + renewal and if the logged in user is not the same as circ->usr, then + the logged in user must have RENEW_CIRC permissions. + NOTES __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.checkout.full"); + method => "run_method", + api_name => "open-ils.circ.checkout.full"); __PACKAGE__->register_method( - method => "run_method", - api_name => "open-ils.circ.checkout.full.override"); + method => "run_method", + api_name => "open-ils.circ.checkout.full.override"); sub run_method { - my( $self, $conn, $auth, $args ) = @_; - translate_legacy_args($args); - my $api = $self->api_name; - - my $circulator = - OpenILS::Application::Circ::Circulator->new($auth, %$args); - - return circ_events($circulator) if $circulator->bail_out; - - # -------------------------------------------------------------------------- - # Go ahead and load the script runner to make sure we have all - # of the objects we need - # -------------------------------------------------------------------------- - $circulator->is_renewal(1) if $api =~ /renew/; - $circulator->is_checkin(1) if $api =~ /checkin/; - $circulator->mk_script_runner; - return circ_events($circulator) if $circulator->bail_out; - - $circulator->circ_permit_patron($scripts{circ_permit_patron}); - $circulator->circ_permit_copy($scripts{circ_permit_copy}); - $circulator->circ_duration($scripts{circ_duration}); - $circulator->circ_permit_renew($scripts{circ_permit_renew}); - - $circulator->override(1) if $api =~ /override/o; - - if( $api =~ /checkout\.permit/ ) { - $circulator->do_permit(); - - } elsif( $api =~ /checkout.full/ ) { - - $circulator->do_permit(); - unless( $circulator->bail_out ) { - $circulator->events([]); - $circulator->do_checkout(); - } - - } elsif( $api =~ /checkout/ ) { - $circulator->do_checkout(); - - } elsif( $api =~ /checkin/ ) { - $circulator->do_checkin(); - - } elsif( $api =~ /renew/ ) { - $circulator->is_renewal(1); - $circulator->do_renew(); - } - - if( $circulator->bail_out ) { - - my @ee; - # make sure no success event accidentally slip in - $circulator->events( - [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]); - - # Log the events - my @e = @{$circulator->events}; - push( @ee, $_->{textcode} ) for @e; - $logger->info("circulator: bailing out with events: @ee"); - - $circulator->editor->rollback; - - } else { - $circulator->editor->commit; - } - - $circulator->script_runner->cleanup; - - $conn->respond_complete(circ_events($circulator)); - - unless($circulator->bail_out) { - $circulator->do_hold_notify($circulator->notify_hold) - if $circulator->notify_hold; - } + my( $self, $conn, $auth, $args ) = @_; + translate_legacy_args($args); + my $api = $self->api_name; + + my $circulator = + OpenILS::Application::Circ::Circulator->new($auth, %$args); + + return circ_events($circulator) if $circulator->bail_out; + + # -------------------------------------------------------------------------- + # Go ahead and load the script runner to make sure we have all + # of the objects we need + # -------------------------------------------------------------------------- + $circulator->is_renewal(1) if $api =~ /renew/; + $circulator->is_checkin(1) if $api =~ /checkin/; + $circulator->mk_script_runner; + return circ_events($circulator) if $circulator->bail_out; + + $circulator->circ_permit_patron($scripts{circ_permit_patron}); + $circulator->circ_permit_copy($scripts{circ_permit_copy}); + $circulator->circ_duration($scripts{circ_duration}); + $circulator->circ_permit_renew($scripts{circ_permit_renew}); + + $circulator->override(1) if $api =~ /override/o; + + if( $api =~ /checkout\.permit/ ) { + $circulator->do_permit(); + + } elsif( $api =~ /checkout.full/ ) { + + $circulator->do_permit(); + unless( $circulator->bail_out ) { + $circulator->events([]); + $circulator->do_checkout(); + } + + } elsif( $api =~ /checkout/ ) { + $circulator->do_checkout(); + + } elsif( $api =~ /checkin/ ) { + $circulator->do_checkin(); + + } elsif( $api =~ /renew/ ) { + $circulator->is_renewal(1); + $circulator->do_renew(); + } + + if( $circulator->bail_out ) { + + my @ee; + # make sure no success event accidentally slip in + $circulator->events( + [ grep { $_->{textcode} ne 'SUCCESS' } @{$circulator->events} ]); + + # Log the events + my @e = @{$circulator->events}; + push( @ee, $_->{textcode} ) for @e; + $logger->info("circulator: bailing out with events: @ee"); + + $circulator->editor->rollback; + + } else { + $circulator->editor->commit; + } + + $circulator->script_runner->cleanup; + + $conn->respond_complete(circ_events($circulator)); + + unless($circulator->bail_out) { + $circulator->do_hold_notify($circulator->notify_hold) + if $circulator->notify_hold; + } } sub circ_events { - my $circ = shift; - my @e = @{$circ->events}; - # if we have multiple events, SUCCESS should not be one of them; - @e = grep { $_->{textcode} ne 'SUCCESS' } @e if @e > 1; - return (@e == 1) ? $e[0] : \@e; + my $circ = shift; + my @e = @{$circ->events}; + # if we have multiple events, SUCCESS should not be one of them; + @e = grep { $_->{textcode} ne 'SUCCESS' } @e if @e > 1; + return (@e == 1) ? $e[0] : \@e; } sub translate_legacy_args { - my $args = shift; - - if( $$args{barcode} ) { - $$args{copy_barcode} = $$args{barcode}; - delete $$args{barcode}; - } - - if( $$args{copyid} ) { - $$args{copy_id} = $$args{copyid}; - delete $$args{copyid}; - } - - if( $$args{patronid} ) { - $$args{patron_id} = $$args{patronid}; - delete $$args{patronid}; - } - - if( $$args{patron} and !ref($$args{patron}) ) { - $$args{patron_id} = $$args{patron}; - delete $$args{patron}; - } - - - if( $$args{noncat} ) { - $$args{is_noncat} = $$args{noncat}; - delete $$args{noncat}; - } - - if( $$args{precat} ) { - $$args{is_precat} = $$args{precat}; - delete $$args{precat}; - } + my $args = shift; + + if( $$args{barcode} ) { + $$args{copy_barcode} = $$args{barcode}; + delete $$args{barcode}; + } + + if( $$args{copyid} ) { + $$args{copy_id} = $$args{copyid}; + delete $$args{copyid}; + } + + if( $$args{patronid} ) { + $$args{patron_id} = $$args{patronid}; + delete $$args{patronid}; + } + + if( $$args{patron} and !ref($$args{patron}) ) { + $$args{patron_id} = $$args{patron}; + delete $$args{patron}; + } + + + if( $$args{noncat} ) { + $$args{is_noncat} = $$args{noncat}; + delete $$args{noncat}; + } + + if( $$args{precat} ) { + $$args{is_precat} = $$args{precat}; + delete $$args{precat}; + } + } @@ -282,9 +283,9 @@ use OpenILS::Utils::CStoreEditor qw/:funcs/; use OpenILS::Application::Circ::ScriptBuilder; use OpenILS::Const qw/:const/; -my $U = "OpenILS::Application::AppUtils"; -my $holdcode = "OpenILS::Application::Circ::Holds"; -my $transcode = "OpenILS::Application::Circ::Transit"; +my $U = "OpenILS::Application::AppUtils"; +my $holdcode = "OpenILS::Application::Circ::Holds"; +my $transcode = "OpenILS::Application::Circ::Transit"; sub DESTROY { } @@ -293,109 +294,117 @@ sub DESTROY { } # Add a pile of automagic getter/setter methods # -------------------------------------------------------------------------- my @AUTOLOAD_FIELDS = qw/ - notify_hold - penalty_request - remote_hold - backdate - copy - copy_id - copy_barcode - patron - patron_id - patron_barcode - script_runner - volume - title - is_renewal - is_noncat - is_precat - is_checkin - noncat_type - editor - events - cache_handle - override - circ_permit_patron - circ_permit_copy - circ_duration - circ_recurring_fines - circ_max_fines - circ_permit_renew - circ - transit - hold - permit_key - noncat_circ_lib - noncat_count - checkout_time - dummy_title - dummy_author - circ_lib - barcode + notify_hold + penalty_request + remote_hold + backdate + copy + copy_id + copy_barcode + patron + patron_id + patron_barcode + script_runner + volume + title + is_renewal + is_noncat + is_precat + is_checkin + noncat_type + editor + events + cache_handle + override + circ_permit_patron + circ_permit_copy + circ_duration + circ_recurring_fines + circ_max_fines + circ_permit_renew + circ + transit + hold + permit_key + noncat_circ_lib + noncat_count + checkout_time + dummy_title + dummy_author + circ_lib + barcode duration_level recurring_fines_level duration_rule recurring_fines_rule max_fine_rule - renewal_remaining - due_date - fulfilled_holds - transit - checkin_changed - force - old_circ - permit_override - pending_checkouts + renewal_remaining + due_date + fulfilled_holds + transit + checkin_changed + force + old_circ + permit_override + pending_checkouts + cancelled_hold_transit + opac_renewal + phone_renewal + desk_renewal /; sub AUTOLOAD { - my $self = shift; - my $type = ref($self) or die "$self is not an object"; - my $data = shift; - my $name = $AUTOLOAD; - $name =~ s/.*://o; - - unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) { - $logger->error("circulator: $type: invalid autoload field: $name"); - die "$type: invalid autoload field: $name\n" - } - - { - no strict 'refs'; - *{"${type}::${name}"} = sub { - my $s = shift; - my $v = shift; - $s->{$name} = $v if defined $v; - return $s->{$name}; - } - } - return $self->$name($data); + my $self = shift; + my $type = ref($self) or die "$self is not an object"; + my $data = shift; + my $name = $AUTOLOAD; + $name =~ s/.*://o; + + unless (grep { $_ eq $name } @AUTOLOAD_FIELDS) { + $logger->error("circulator: $type: invalid autoload field: $name"); + die "$type: invalid autoload field: $name\n" + } + + { + no strict 'refs'; + *{"${type}::${name}"} = sub { + my $s = shift; + my $v = shift; + $s->{$name} = $v if defined $v; + return $s->{$name}; + } + } + return $self->$name($data); } sub new { - my( $class, $auth, %args ) = @_; - $class = ref($class) || $class; - my $self = bless( {}, $class ); + my( $class, $auth, %args ) = @_; + $class = ref($class) || $class; + my $self = bless( {}, $class ); + + $self->events([]); + $self->editor( + new_editor(xact => 1, authtoken => $auth) ); - $self->events([]); - $self->editor( - new_editor(xact => 1, authtoken => $auth) ); + unless( $self->editor->checkauth ) { + $self->bail_on_events($self->editor->event); + return $self; + } - unless( $self->editor->checkauth ) { - $self->bail_on_events($self->editor->event); - return $self; - } + $self->cache_handle(OpenSRF::Utils::Cache->new('global')); - $self->cache_handle(OpenSRF::Utils::Cache->new('global')); + $self->$_($args{$_}) for keys %args; - $self->$_($args{$_}) for keys %args; + $self->circ_lib( + ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou); - $self->circ_lib( - ($self->circ_lib) ? $self->circ_lib : $self->editor->requestor->ws_ou); + # if this is a renewal, default to desk_renewal + $self->desk_renewal(1) unless + $self->opac_renewal or $self->phone_renewal; - return $self; + return $self; } @@ -403,40 +412,40 @@ sub new { # True if we should discontinue processing # -------------------------------------------------------------------------- sub bail_out { - my( $self, $bool ) = @_; - if( defined $bool ) { - $logger->info("circulator: BAILING OUT") if $bool; - $self->{bail_out} = $bool; - } - return $self->{bail_out}; + my( $self, $bool ) = @_; + if( defined $bool ) { + $logger->info("circulator: BAILING OUT") if $bool; + $self->{bail_out} = $bool; + } + return $self->{bail_out}; } sub push_events { - my( $self, @evts ) = @_; - for my $e (@evts) { - next unless $e; - $logger->info("circulator: pushing event ".$e->{textcode}); - push( @{$self->events}, $e ) unless - grep { $_->{textcode} eq $e->{textcode} } @{$self->events}; - } + my( $self, @evts ) = @_; + for my $e (@evts) { + next unless $e; + $logger->info("circulator: pushing event ".$e->{textcode}); + push( @{$self->events}, $e ) unless + grep { $_->{textcode} eq $e->{textcode} } @{$self->events}; + } } sub mk_permit_key { - my $self = shift; - my $key = md5_hex( time() . rand() . "$$" ); - $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 ); - return $self->permit_key($key); + my $self = shift; + my $key = md5_hex( time() . rand() . "$$" ); + $self->cache_handle->put_cache( "oils_permit_key_$key", 1, 300 ); + return $self->permit_key($key); } sub check_permit_key { - my $self = shift; - my $key = $self->permit_key; - return 0 unless $key; - my $k = "oils_permit_key_$key"; - my $one = $self->cache_handle->get_cache($k); - $self->cache_handle->delete_cache($k); - return ($one) ? 1 : 0; + my $self = shift; + my $key = $self->permit_key; + return 0 unless $key; + my $k = "oils_permit_key_$key"; + my $one = $self->cache_handle->get_cache($k); + $self->cache_handle->delete_cache($k); + return ($one) ? 1 : 0; } @@ -445,74 +454,74 @@ sub check_permit_key { # objects we need # -------------------------------------------------------------------------- sub mk_script_runner { - my $self = shift; - my $args = {}; + my $self = shift; + my $args = {}; - my @fields = - qw/copy copy_barcode copy_id patron - patron_id patron_barcode volume title editor/; + my @fields = + qw/copy copy_barcode copy_id patron + patron_id patron_barcode volume title editor/; - # Translate our objects into the ScriptBuilder args hash - $$args{$_} = $self->$_() for @fields; + # Translate our objects into the ScriptBuilder args hash + $$args{$_} = $self->$_() for @fields; - $args->{ignore_user_status} = 1 if $self->is_checkin; - $$args{fetch_patron_by_circ_copy} = 1; - $$args{fetch_patron_circ_info} = 1 unless $self->is_checkin; + $args->{ignore_user_status} = 1 if $self->is_checkin; + $$args{fetch_patron_by_circ_copy} = 1; + $$args{fetch_patron_circ_info} = 1 unless $self->is_checkin; - if( my $pco = $self->pending_checkouts ) { - $logger->info("circulator: we were given a pending checkouts number of $pco"); - $$args{patronItemsOut} = $pco; - } + if( my $pco = $self->pending_checkouts ) { + $logger->info("circulator: we were given a pending checkouts number of $pco"); + $$args{patronItemsOut} = $pco; + } - # This fetches most of the objects we need - $self->script_runner( - OpenILS::Application::Circ::ScriptBuilder->build($args)); + # This fetches most of the objects we need + $self->script_runner( + OpenILS::Application::Circ::ScriptBuilder->build($args)); - # Now we translate the ScriptBuilder objects back into self - $self->$_($$args{$_}) for @fields; + # Now we translate the ScriptBuilder objects back into self + $self->$_($$args{$_}) for @fields; - my @evts = @{$args->{_events}} if $args->{_events}; + my @evts = @{$args->{_events}} if $args->{_events}; - $logger->debug("circulator: script builder returned events: @evts") if @evts; + $logger->debug("circulator: script builder returned events: @evts") if @evts; - if(@evts) { - # Anything besides ASSET_COPY_NOT_FOUND will stop processing - if(!$self->is_noncat and - @evts == 1 and - $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') { - $self->is_precat(1); + if(@evts) { + # Anything besides ASSET_COPY_NOT_FOUND will stop processing + if(!$self->is_noncat and + @evts == 1 and + $evts[0]->{textcode} eq 'ASSET_COPY_NOT_FOUND') { + $self->is_precat(1); - } else { - my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts; - return $self->bail_on_events(@e); - } - } + } else { + my @e = grep { $_->{textcode} ne 'ASSET_COPY_NOT_FOUND' } @evts; + return $self->bail_on_events(@e); + } + } - $self->is_precat(1) if $self->copy - and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER; + $self->is_precat(1) if $self->copy + and $self->copy->call_number == OILS_PRECAT_CALL_NUMBER; - # We can't renew if there is no copy - return $self->bail_on_events(@evts) if - $self->is_renewal and !$self->copy; + # We can't renew if there is no copy + return $self->bail_on_events(@evts) if + $self->is_renewal and !$self->copy; - # Set some circ-specific flags in the script environment - my $evt = "environment"; - $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef); + # Set some circ-specific flags in the script environment + my $evt = "environment"; + $self->script_runner->insert("$evt.isRenewal", ($self->is_renewal) ? 1 : undef); - if( $self->is_noncat ) { + if( $self->is_noncat ) { $self->script_runner->insert("$evt.isNonCat", 1); $self->script_runner->insert("$evt.nonCatType", $self->noncat_type); - } + } - if( $self->is_precat ) { - $self->script_runner->insert("environment.isPrecat", 1, 1); - } + if( $self->is_precat ) { + $self->script_runner->insert("environment.isPrecat", 1, 1); + } - $self->script_runner->add_path( $_ ) for @$script_libs; + $self->script_runner->add_path( $_ ) for @$script_libs; - return 1; + return 1; } @@ -522,35 +531,35 @@ sub mk_script_runner { # Does the circ permit work # -------------------------------------------------------------------------- sub do_permit { - my $self = shift; - - $self->log_me("do_permit()"); - - unless( $self->editor->requestor->id == $self->patron->id ) { - return $self->bail_on_events($self->editor->event) - unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') ); - } - - $self->check_captured_holds(); - $self->do_copy_checks(); - return if $self->bail_out; - $self->run_patron_permit_scripts(); - $self->run_copy_permit_scripts() - unless $self->is_precat or $self->is_noncat; - $self->override_events() unless $self->is_renewal; - return if $self->bail_out; - - if( $self->is_precat ) { - $self->push_events( - OpenILS::Event->new( - 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key)); - return $self->bail_out(1) unless $self->is_renewal; - } - - $self->push_events( + my $self = shift; + + $self->log_me("do_permit()"); + + unless( $self->editor->requestor->id == $self->patron->id ) { + return $self->bail_on_events($self->editor->event) + unless( $self->editor->allowed('VIEW_PERMIT_CHECKOUT') ); + } + + $self->check_captured_holds(); + $self->do_copy_checks(); + return if $self->bail_out; + $self->run_patron_permit_scripts(); + $self->run_copy_permit_scripts() + unless $self->is_precat or $self->is_noncat; + $self->override_events() unless $self->is_renewal; + return if $self->bail_out; + + if( $self->is_precat ) { + $self->push_events( + OpenILS::Event->new( + 'ITEM_NOT_CATALOGED', payload => $self->mk_permit_key)); + return $self->bail_out(1) unless $self->is_renewal; + } + + $self->push_events( OpenILS::Event->new( - 'SUCCESS', - payload => $self->mk_permit_key)); + 'SUCCESS', + payload => $self->mk_permit_key)); } @@ -559,84 +568,84 @@ sub check_captured_holds { my $copy = $self->copy; my $patron = $self->patron; - return undef unless $copy; + return undef unless $copy; - my $s = $U->copy_status($copy->status)->id; - return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF; - $logger->info("circulator: copy is on holds shelf, searching for the correct hold"); + my $s = $U->copy_status($copy->status)->id; + return unless $s == OILS_COPY_STATUS_ON_HOLDS_SHELF; + $logger->info("circulator: copy is on holds shelf, searching for the correct hold"); - # Item is on the holds shelf, make sure it's going to the right person - my $holds = $self->editor->search_action_hold_request( - [ - { - current_copy => $copy->id , - capture_time => { '!=' => undef }, - cancel_time => undef, - fulfillment_time => undef - }, - { limit => 1 } - ] - ); + # Item is on the holds shelf, make sure it's going to the right person + my $holds = $self->editor->search_action_hold_request( + [ + { + current_copy => $copy->id , + capture_time => { '!=' => undef }, + cancel_time => undef, + fulfillment_time => undef + }, + { limit => 1 } + ] + ); - if( $holds and $$holds[0] ) { - return undef if $$holds[0]->usr == $patron->id; - } + if( $holds and $$holds[0] ) { + return undef if $$holds[0]->usr == $patron->id; + } - $logger->info("circulator: this copy is needed by a different patron to fulfill a hold"); + $logger->info("circulator: this copy is needed by a different patron to fulfill a hold"); - $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF')); + $self->push_events(OpenILS::Event->new('ITEM_ON_HOLDS_SHELF')); } sub do_copy_checks { - my $self = shift; - my $copy = $self->copy; - return unless $copy; + my $self = shift; + my $copy = $self->copy; + return unless $copy; - my $stat = $U->copy_status($copy->status)->id; + my $stat = $U->copy_status($copy->status)->id; - # We cannot check out a copy if it is in-transit - if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) { - return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT')); - } + # We cannot check out a copy if it is in-transit + if( $stat == OILS_COPY_STATUS_IN_TRANSIT ) { + return $self->bail_on_events(OpenILS::Event->new('COPY_IN_TRANSIT')); + } - $self->handle_claims_returned(); - return if $self->bail_out; + $self->handle_claims_returned(); + return if $self->bail_out; - # no claims returned circ was found, check if there is any open circ - unless( $self->is_renewal ) { - my $circs = $self->editor->search_action_circulation( - { target_copy => $copy->id, checkin_time => undef } - ); + # no claims returned circ was found, check if there is any open circ + unless( $self->is_renewal ) { + my $circs = $self->editor->search_action_circulation( + { target_copy => $copy->id, checkin_time => undef } + ); - return $self->bail_on_events( - OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs; - } + return $self->bail_on_events( + OpenILS::Event->new('OPEN_CIRCULATION_EXISTS')) if @$circs; + } } sub send_penalty_request { - my $self = shift; - my $ses = OpenSRF::AppSession->create('open-ils.penalty'); - $self->penalty_request( - $ses->request( - 'open-ils.penalty.patron_penalty.calculate', - { update => 1, - authtoken => $self->editor->authtoken, - patron => $self->patron } ) ); + my $self = shift; + my $ses = OpenSRF::AppSession->create('open-ils.penalty'); + $self->penalty_request( + $ses->request( + 'open-ils.penalty.patron_penalty.calculate', + { update => 1, + authtoken => $self->editor->authtoken, + patron => $self->patron } ) ); } sub gather_penalty_request { - my $self = shift; - return [] unless $self->penalty_request; - my $data = $self->penalty_request->recv; - if( ref $data ) { - throw $data if UNIVERSAL::isa($data,'Error'); - $data = $data->content; - return $data->{fatal_penalties}; - } - $logger->error("circulator: penalty request returned no data"); - return []; + my $self = shift; + return [] unless $self->penalty_request; + my $data = $self->penalty_request->recv; + if( ref $data ) { + throw $data if UNIVERSAL::isa($data,'Error'); + $data = $data->content; + return $data->{fatal_penalties}; + } + $logger->error("circulator: penalty request returned no data"); + return []; } # --------------------------------------------------------------------- @@ -644,60 +653,60 @@ sub gather_penalty_request { # set bail_out for any events # --------------------------------------------------------------------- sub run_patron_permit_scripts { - my $self = shift; - my $runner = $self->script_runner; - my $patronid = $self->patron->id; + my $self = shift; + my $runner = $self->script_runner; + my $patronid = $self->patron->id; - $self->send_penalty_request() unless $self->is_renewal; + $self->send_penalty_request() unless $self->is_renewal; - # --------------------------------------------------------------------- - # Now run the patron permit script - # --------------------------------------------------------------------- - $runner->load($self->circ_permit_patron); - my $result = $runner->run or - throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@"); + # --------------------------------------------------------------------- + # Now run the patron permit script + # --------------------------------------------------------------------- + $runner->load($self->circ_permit_patron); + my $result = $runner->run or + throw OpenSRF::EX::ERROR ("Circ Permit Patron Script Died: $@"); - my $patron_events = $result->{events}; - my @allevents; + my $patron_events = $result->{events}; + my @allevents; - # --------------------------------------------------------------------- - # this is policy directly in the code, not a good idea in general, but - # the penalty server doesn't know anything about renewals, so we - # have to strip the event out here - my $penalties = ($self->is_renewal) ? [] : $self->gather_penalty_request(); - # --------------------------------------------------------------------- + # --------------------------------------------------------------------- + # this is policy directly in the code, not a good idea in general, but + # the penalty server doesn't know anything about renewals, so we + # have to strip the event out here + my $penalties = ($self->is_renewal) ? [] : $self->gather_penalty_request(); + # --------------------------------------------------------------------- - push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events); + push( @allevents, OpenILS::Event->new($_)) for (@$penalties, @$patron_events); - $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents; + $logger->info("circulator: permit_patron script returned events: @allevents") if @allevents; - $self->push_events(@allevents); + $self->push_events(@allevents); } sub run_copy_permit_scripts { - my $self = shift; - my $copy = $self->copy || return; - my $runner = $self->script_runner; - + my $self = shift; + my $copy = $self->copy || return; + my $runner = $self->script_runner; + # --------------------------------------------------------------------- # Capture all of the copy permit events # --------------------------------------------------------------------- $runner->load($self->circ_permit_copy); my $result = $runner->run or - throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@"); + throw OpenSRF::EX::ERROR ("Circ Permit Copy Script Died: $@"); my $copy_events = $result->{events}; # --------------------------------------------------------------------- # Now collect all of the events together # --------------------------------------------------------------------- - my @allevents; + my @allevents; push( @allevents, OpenILS::Event->new($_)) for @$copy_events; - # See if this copy has an alert message - my $ae = $self->check_copy_alert(); - push( @allevents, $ae ) if $ae; + # See if this copy has an alert message + my $ae = $self->check_copy_alert(); + push( @allevents, $ae ) if $ae; # uniquify the events my %hash = map { ($_->{ilsevent} => $_) } @allevents; @@ -705,22 +714,22 @@ sub run_copy_permit_scripts { for (@allevents) { $_->{payload} = $copy if - ($_->{textcode} eq 'COPY_NOT_AVAILABLE'); + ($_->{textcode} eq 'COPY_NOT_AVAILABLE'); } - $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents; + $logger->info("circulator: permit_copy script returned events: @allevents") if @allevents; - $self->push_events(@allevents); + $self->push_events(@allevents); } sub check_copy_alert { - my $self = shift; - return undef if $self->is_renewal; - return OpenILS::Event->new( - 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message) - if $self->copy and $self->copy->alert_message; - return undef; + my $self = shift; + return undef if $self->is_renewal; + return OpenILS::Event->new( + 'COPY_ALERT_MESSAGE', payload => $self->copy->alert_message) + if $self->copy and $self->copy->alert_message; + return undef; } @@ -734,65 +743,65 @@ sub check_copy_alert { # that are being force-checked out # -------------------------------------------------------------------------- sub override_events { - my $self = shift; - my @events = @{$self->events}; - return unless @events; + my $self = shift; + my @events = @{$self->events}; + return unless @events; - if(!$self->override) { - return $self->bail_out(1) - if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' ); - } + if(!$self->override) { + return $self->bail_out(1) + if( @events > 1 or $events[0]->{textcode} ne 'SUCCESS' ); + } - $self->events([]); - + $self->events([]); + for my $e (@events) { my $tc = $e->{textcode}; next if $tc eq 'SUCCESS'; my $ov = "$tc.override"; $logger->info("circulator: attempting to override event: $ov"); - return $self->bail_on_events($self->editor->event) - unless( $self->editor->allowed($ov) ); + return $self->bail_on_events($self->editor->event) + unless( $self->editor->allowed($ov) ); } } - + # -------------------------------------------------------------------------- # If there is an open claimsreturn circ on the requested copy, close the # circ if overriding, otherwise bail out # -------------------------------------------------------------------------- sub handle_claims_returned { - my $self = shift; - my $copy = $self->copy; + my $self = shift; + my $copy = $self->copy; - my $CR = $self->editor->search_action_circulation( - { - target_copy => $copy->id, - stop_fines => OILS_STOP_FINES_CLAIMSRETURNED, - checkin_time => undef, - } - ); + my $CR = $self->editor->search_action_circulation( + { + target_copy => $copy->id, + stop_fines => OILS_STOP_FINES_CLAIMSRETURNED, + checkin_time => undef, + } + ); - return unless ($CR = $CR->[0]); + return unless ($CR = $CR->[0]); - my $evt; + my $evt; - # - If the caller has set the override flag, we will check the item in - if($self->override) { + # - If the caller has set the override flag, we will check the item in + if($self->override) { - $CR->checkin_time('now'); - $CR->checkin_lib($self->editor->requestor->ws_ou); - $CR->checkin_staff($self->editor->requestor->id); + $CR->checkin_time('now'); + $CR->checkin_lib($self->editor->requestor->ws_ou); + $CR->checkin_staff($self->editor->requestor->id); - $evt = $self->editor->event - unless $self->editor->update_action_circulation($CR); + $evt = $self->editor->event + unless $self->editor->update_action_circulation($CR); - } else { - $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED'); - } + } else { + $evt = OpenILS::Event->new('CIRC_CLAIMS_RETURNED'); + } - $self->bail_on_events($evt) if $evt; - return; + $self->bail_on_events($evt) if $evt; + return; } @@ -800,123 +809,123 @@ sub handle_claims_returned { # This performs the checkout # -------------------------------------------------------------------------- sub do_checkout { - my $self = shift; + my $self = shift; - $self->log_me("do_checkout()"); + $self->log_me("do_checkout()"); - # make sure perms are good if this isn't a renewal - unless( $self->is_renewal ) { - return $self->bail_on_events($self->editor->event) - unless( $self->editor->allowed('COPY_CHECKOUT') ); - } + # make sure perms are good if this isn't a renewal + unless( $self->is_renewal ) { + return $self->bail_on_events($self->editor->event) + unless( $self->editor->allowed('COPY_CHECKOUT') ); + } - # verify the permit key - unless( $self->check_permit_key ) { - if( $self->permit_override ) { - return $self->bail_on_events($self->editor->event) - unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE'); - } else { - return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')) - } - } + # verify the permit key + unless( $self->check_permit_key ) { + if( $self->permit_override ) { + return $self->bail_on_events($self->editor->event) + unless $self->editor->allowed('CIRC_PERMIT_OVERRIDE'); + } else { + return $self->bail_on_events(OpenILS::Event->new('CIRC_PERMIT_BAD_KEY')) + } + } - # if this is a non-cataloged circ, build the circ and finish - if( $self->is_noncat ) { - $self->checkout_noncat; - $self->push_events( - OpenILS::Event->new('SUCCESS', - payload => { noncat_circ => $self->circ })); - return; - } + # if this is a non-cataloged circ, build the circ and finish + if( $self->is_noncat ) { + $self->checkout_noncat; + $self->push_events( + OpenILS::Event->new('SUCCESS', + payload => { noncat_circ => $self->circ })); + return; + } - if( $self->is_precat ) { - $self->script_runner->insert("environment.isPrecat", 1, 1); - $self->make_precat_copy; - return if $self->bail_out; + if( $self->is_precat ) { + $self->script_runner->insert("environment.isPrecat", 1, 1); + $self->make_precat_copy; + return if $self->bail_out; - } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) { - return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED')); - } + } elsif( $self->copy->call_number == OILS_PRECAT_CALL_NUMBER ) { + return $self->bail_on_events(OpenILS::Event->new('ITEM_NOT_CATALOGED')); + } - $self->do_copy_checks; - return if $self->bail_out; + $self->do_copy_checks; + return if $self->bail_out; - $self->run_checkout_scripts(); - return if $self->bail_out; + $self->run_checkout_scripts(); + return if $self->bail_out; - $self->build_checkout_circ_object(); - return if $self->bail_out; + $self->build_checkout_circ_object(); + return if $self->bail_out; - $self->apply_modified_due_date(); - return if $self->bail_out; + $self->apply_modified_due_date(); + return if $self->bail_out; - return $self->bail_on_events($self->editor->event) - unless $self->editor->create_action_circulation($self->circ); + return $self->bail_on_events($self->editor->event) + unless $self->editor->create_action_circulation($self->circ); - # refresh the circ to force local time zone for now - $self->circ($self->editor->retrieve_action_circulation($self->circ->id)); + # refresh the circ to force local time zone for now + $self->circ($self->editor->retrieve_action_circulation($self->circ->id)); - $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT); - $self->update_copy; - return if $self->bail_out; + $self->copy->status(OILS_COPY_STATUS_CHECKED_OUT); + $self->update_copy; + return if $self->bail_out; - $self->handle_checkout_holds(); - return if $self->bail_out; + $self->handle_checkout_holds(); + return if $self->bail_out; # ------------------------------------------------------------------------------ # Update the patron penalty info in the DB. Run it for permit-overrides or - # renewals since both of those cases do not require the penalty server to - # run during the permit phase of the checkout + # renewals since both of those cases do not require the penalty server to + # run during the permit phase of the checkout # ------------------------------------------------------------------------------ - if( $self->permit_override or $self->is_renewal ) { - $U->update_patron_penalties( - authtoken => $self->editor->authtoken, - patron => $self->patron, - background => 1, - ); - } - - my $record = $U->record_to_mvr($self->title) unless $self->is_precat; - $self->push_events( - OpenILS::Event->new('SUCCESS', - payload => { - copy => $U->unflesh_copy($self->copy), - circ => $self->circ, - record => $record, - holds_fulfilled => $self->fulfilled_holds, - } - ) - ); + if( $self->permit_override or $self->is_renewal ) { + $U->update_patron_penalties( + authtoken => $self->editor->authtoken, + patron => $self->patron, + background => 1, + ); + } + + my $record = $U->record_to_mvr($self->title) unless $self->is_precat; + $self->push_events( + OpenILS::Event->new('SUCCESS', + payload => { + copy => $U->unflesh_copy($self->copy), + circ => $self->circ, + record => $record, + holds_fulfilled => $self->fulfilled_holds, + } + ) + ); } sub update_copy { - my $self = shift; - my $copy = $self->copy; - - my $stat = $copy->status if ref $copy->status; - my $loc = $copy->location if ref $copy->location; - my $circ_lib = $copy->circ_lib if ref $copy->circ_lib; - - $copy->status($stat->id) if $stat; - $copy->location($loc->id) if $loc; - $copy->circ_lib($circ_lib->id) if $circ_lib; - $copy->editor($self->editor->requestor->id); - $copy->edit_date('now'); - $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect; - - return $self->bail_on_events($self->editor->event) - unless $self->editor->update_asset_copy($self->copy); - - $copy->status($U->copy_status($copy->status)); - $copy->location($loc) if $loc; - $copy->circ_lib($circ_lib) if $circ_lib; + my $self = shift; + my $copy = $self->copy; + + my $stat = $copy->status if ref $copy->status; + my $loc = $copy->location if ref $copy->location; + my $circ_lib = $copy->circ_lib if ref $copy->circ_lib; + + $copy->status($stat->id) if $stat; + $copy->location($loc->id) if $loc; + $copy->circ_lib($circ_lib->id) if $circ_lib; + $copy->editor($self->editor->requestor->id); + $copy->edit_date('now'); + $copy->age_protect($copy->age_protect->id) if ref $copy->age_protect; + + return $self->bail_on_events($self->editor->event) + unless $self->editor->update_asset_copy($self->copy); + + $copy->status($U->copy_status($copy->status)); + $copy->location($loc) if $loc; + $copy->circ_lib($circ_lib) if $circ_lib; } sub bail_on_events { - my( $self, @evts ) = @_; - $self->push_events(@evts); - $self->bail_out(1); + my( $self, @evts ) = @_; + $self->push_events(@evts); + $self->bail_out(1); } sub handle_checkout_holds { @@ -925,13 +934,13 @@ sub handle_checkout_holds { my $copy = $self->copy; my $patron = $self->patron; - my $holds = $self->editor->search_action_hold_request( - { - current_copy => $copy->id , - cancel_time => undef, - fulfillment_time => undef - } - ); + my $holds = $self->editor->search_action_hold_request( + { + current_copy => $copy->id , + cancel_time => undef, + fulfillment_time => undef + } + ); my @fulfilled; @@ -953,17 +962,17 @@ sub handle_checkout_holds { # if the hold was never officially captured, capture it. $hold->capture_time('now') unless $hold->capture_time; - # just make sure it's set correctly + # just make sure it's set correctly $hold->current_copy($copy->id); $hold->fulfillment_time('now'); - $hold->fulfillment_staff($self->editor->requestor->id); - $hold->fulfillment_lib($self->editor->requestor->ws_ou); + $hold->fulfillment_staff($self->editor->requestor->id); + $hold->fulfillment_lib($self->editor->requestor->ws_ou); - return $self->bail_on_events($self->editor->event) - unless $self->editor->update_action_hold_request($hold); + return $self->bail_on_events($self->editor->event) + unless $self->editor->update_action_hold_request($hold); - $holdcode->delete_hold_copy_maps($self->editor, $hold->id); + $holdcode->delete_hold_copy_maps($self->editor, $hold->id); push( @fulfilled, $hold->id ); } @@ -975,55 +984,55 @@ sub handle_checkout_holds { $logger->info("circulator: un-targeting hold ".$_->id. " because copy ".$copy->id." is getting checked out"); - # - make the targeter process this hold at next run + # - make the targeter process this hold at next run $_->clear_prev_check_time; - # - clear out the targetted copy + # - clear out the targetted copy $_->clear_current_copy; $_->clear_capture_time; - return $self->bail_on_event($self->editor->event) - unless $self->editor->update_action_hold_request($_); + return $self->bail_on_event($self->editor->event) + unless $self->editor->update_action_hold_request($_); } } - $self->fulfilled_holds(\@fulfilled); + $self->fulfilled_holds(\@fulfilled); } sub run_checkout_scripts { - my $self = shift; + my $self = shift; - my $evt; + my $evt; my $runner = $self->script_runner; $runner->load($self->circ_duration); my $result = $runner->run or - throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@"); + throw OpenSRF::EX::ERROR ("Circ Duration Script Died: $@"); my $duration = $result->{durationRule}; my $recurring = $result->{recurringFinesRule}; my $max_fine = $result->{maxFine}; - if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) { + if( $duration ne OILS_UNLIMITED_CIRC_DURATION ) { - ($duration, $evt) = $U->fetch_circ_duration_by_name($duration); - return $self->bail_on_events($evt) if $evt; - - ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring); - return $self->bail_on_events($evt) if $evt; - - ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine); - return $self->bail_on_events($evt) if $evt; + ($duration, $evt) = $U->fetch_circ_duration_by_name($duration); + return $self->bail_on_events($evt) if $evt; + + ($recurring, $evt) = $U->fetch_recurring_fine_by_name($recurring); + return $self->bail_on_events($evt) if $evt; + + ($max_fine, $evt) = $U->fetch_max_fine_by_name($max_fine); + return $self->bail_on_events($evt) if $evt; - } else { + } else { - # The item circulates with an unlimited duration - $duration = undef; - $recurring = undef; - $max_fine = undef; - } + # The item circulates with an unlimited duration + $duration = undef; + $recurring = undef; + $max_fine = undef; + } $self->duration_rule($duration); $self->recurring_fines_rule($recurring); @@ -1032,7 +1041,7 @@ sub run_checkout_scripts { sub build_checkout_circ_object { - my $self = shift; + my $self = shift; my $circ = Fieldmapper::action::circulation->new; my $duration = $self->duration_rule; @@ -1041,79 +1050,82 @@ sub build_checkout_circ_object { my $copy = $self->copy; my $patron = $self->patron; - if( $duration ) { - - my $dname = $duration->name; - my $mname = $max->name; - my $rname = $recurring->name; - - $logger->debug("circulator: building circulation ". - "with duration=$dname, maxfine=$mname, recurring=$rname"); - - $circ->duration( $duration->shrt ) - if $copy->loan_duration == OILS_CIRC_DURATION_SHORT; - $circ->duration( $duration->normal ) - if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL; - $circ->duration( $duration->extended ) - if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED; - - $circ->recuring_fine( $recurring->low ) - if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW; - $circ->recuring_fine( $recurring->normal ) - if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL; - $circ->recuring_fine( $recurring->high ) - if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH; - - $circ->duration_rule( $duration->name ); - $circ->recuring_fine_rule( $recurring->name ); - $circ->max_fine_rule( $max->name ); - $circ->max_fine( $max->amount ); - - $circ->fine_interval($recurring->recurance_interval); - $circ->renewal_remaining( $duration->max_renewals ); - - } else { - - $logger->info("circulator: copy found with an unlimited circ duration"); - $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION); - $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION); - $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION); - $circ->renewal_remaining(0); - } + if( $duration ) { + + my $dname = $duration->name; + my $mname = $max->name; + my $rname = $recurring->name; + + $logger->debug("circulator: building circulation ". + "with duration=$dname, maxfine=$mname, recurring=$rname"); + + $circ->duration( $duration->shrt ) + if $copy->loan_duration == OILS_CIRC_DURATION_SHORT; + $circ->duration( $duration->normal ) + if $copy->loan_duration == OILS_CIRC_DURATION_NORMAL; + $circ->duration( $duration->extended ) + if $copy->loan_duration == OILS_CIRC_DURATION_EXTENDED; + + $circ->recuring_fine( $recurring->low ) + if $copy->fine_level == OILS_REC_FINE_LEVEL_LOW; + $circ->recuring_fine( $recurring->normal ) + if $copy->fine_level == OILS_REC_FINE_LEVEL_NORMAL; + $circ->recuring_fine( $recurring->high ) + if $copy->fine_level == OILS_REC_FINE_LEVEL_HIGH; + + $circ->duration_rule( $duration->name ); + $circ->recuring_fine_rule( $recurring->name ); + $circ->max_fine_rule( $max->name ); + $circ->max_fine( $max->amount ); + + $circ->fine_interval($recurring->recurance_interval); + $circ->renewal_remaining( $duration->max_renewals ); + + } else { + + $logger->info("circulator: copy found with an unlimited circ duration"); + $circ->duration_rule(OILS_UNLIMITED_CIRC_DURATION); + $circ->recuring_fine_rule(OILS_UNLIMITED_CIRC_DURATION); + $circ->max_fine_rule(OILS_UNLIMITED_CIRC_DURATION); + $circ->renewal_remaining(0); + } $circ->target_copy( $copy->id ); $circ->usr( $patron->id ); $circ->circ_lib( $self->circ_lib ); if( $self->is_renewal ) { - $circ->opac_renewal(1); + $circ->opac_renewal('t') if $self->opac_renewal; + $circ->phone_renewal('t') if $self->phone_renewal; + $circ->desk_renewal('t') if $self->desk_renewal; $circ->renewal_remaining($self->renewal_remaining); $circ->circ_staff($self->editor->requestor->id); } + # if the user provided an overiding checkout time, # (e.g. the checkout really happened several hours ago), then # we apply that here. Does this need a perm?? - $circ->xact_start(clense_ISO8601($self->checkout_time)) - if $self->checkout_time; + $circ->xact_start(clense_ISO8601($self->checkout_time)) + if $self->checkout_time; # if a patron is renewing, 'requestor' will be the patron $circ->circ_staff($self->editor->requestor->id); - $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration; + $circ->due_date( $self->create_due_date($circ->duration) ) if $circ->duration; - $self->circ($circ); + $self->circ($circ); } sub apply_modified_due_date { - my $self = shift; - my $circ = $self->circ; - my $copy = $self->copy; + my $self = shift; + my $circ = $self->circ; + my $copy = $self->copy; if( $self->due_date ) { - return $self->bail_on_events($self->editor->event) - unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib); + return $self->bail_on_events($self->editor->event) + unless $self->editor->allowed('CIRC_OVERRIDE_DUE_DATE', $self->circ_lib); $circ->due_date(clense_ISO8601($self->due_date)); @@ -1122,18 +1134,18 @@ sub apply_modified_due_date { # if the due_date lands on a day when the location is closed return unless $copy and $circ->due_date; - #my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib; + #my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib; - # due-date overlap should be determined by the location the item - # is checked out from, not the owning or circ lib of the item - my $org = $self->editor->requestor->ws_ou; + # due-date overlap should be determined by the location the item + # is checked out from, not the owning or circ lib of the item + my $org = $self->editor->requestor->ws_ou; $logger->info("circulator: circ searching for closed date overlap on lib $org". - " with an item due date of ".$circ->due_date ); + " with an item due date of ".$circ->due_date ); my $dateinfo = $U->storagereq( 'open-ils.storage.actor.org_unit.closed_date.overlap', - $org, $circ->due_date ); + $org, $circ->due_date ); if($dateinfo) { $logger->info("circulator: $dateinfo : circ due data / close date overlap found : due_date=". @@ -1149,7 +1161,7 @@ sub apply_modified_due_date { sub create_due_date { - my( $self, $duration ) = @_; + my( $self, $duration ) = @_; my ($sec,$min,$hour,$mday,$mon,$year) = gmtime(OpenSRF::Utils->interval_to_seconds($duration) + int(time())); $year += 1900; $mon += 1; @@ -1162,8 +1174,8 @@ sub create_due_date { sub make_precat_copy { - my $self = shift; - my $copy = $self->copy; + my $self = shift; + my $copy = $self->copy; if($copy) { $logger->debug("ciculator: Pre-cat copy already exists in checkout: ID=" . $copy->id); @@ -1173,12 +1185,12 @@ sub make_precat_copy { $copy->dummy_title($self->dummy_title); $copy->dummy_author($self->dummy_author); - $self->update_copy(); - return; + $self->update_copy(); + return; } $logger->info("circulator: Creating a new precataloged ". - "copy in checkout with barcode " . $self->copy_barcode); + "copy in checkout with barcode " . $self->copy_barcode); $copy = Fieldmapper::asset::copy->new; $copy->circ_lib($self->circ_lib); @@ -1192,27 +1204,27 @@ sub make_precat_copy { $copy->dummy_title($self->dummy_title || ""); $copy->dummy_author($self->dummy_author || ""); - unless( $self->copy($self->editor->create_asset_copy($copy)) ) { - $self->bail_out(1); - $self->push_events($self->editor->event); - return; - } + unless( $self->copy($self->editor->create_asset_copy($copy)) ) { + $self->bail_out(1); + $self->push_events($self->editor->event); + return; + } - # this is a little bit of a hack, but we need to - # get the copy into the script runner - $self->script_runner->insert("environment.copy", $copy, 1); + # this is a little bit of a hack, but we need to + # get the copy into the script runner + $self->script_runner->insert("environment.copy", $copy, 1); } sub checkout_noncat { - my $self = shift; + my $self = shift; - my $circ; - my $evt; + my $circ; + my $evt; - my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou; - my $count = $self->noncat_count || 1; - my $cotime = clense_ISO8601($self->checkout_time) || ""; + my $lib = $self->noncat_circ_lib || $self->editor->requestor->ws_ou; + my $count = $self->noncat_count || 1; + my $cotime = clense_ISO8601($self->checkout_time) || ""; $logger->info("ciculator: circ creating $count noncat circs with checkout time $cotime"); @@ -1220,169 +1232,190 @@ sub checkout_noncat { ( $circ, $evt ) = OpenILS::Application::Circ::NonCat::create_non_cat_circ( $self->editor->requestor->id, - $self->patron->id, - $lib, - $self->noncat_type, - $cotime, - $self->editor ); - - if( $evt ) { - $self->push_events($evt); - $self->bail_out(1); - return; - } - $self->circ($circ); + $self->patron->id, + $lib, + $self->noncat_type, + $cotime, + $self->editor ); + + if( $evt ) { + $self->push_events($evt); + $self->bail_out(1); + return; + } + $self->circ($circ); } } sub do_checkin { - my $self = shift; - $self->log_me("do_checkin()"); - - - return $self->bail_on_events( - OpenILS::Event->new('ASSET_COPY_NOT_FOUND')) - unless $self->copy; - - if( $self->checkin_check_holds_shelf() ) { - $self->bail_on_events(OpenILS::Event->new('NO_CHANGE')); - $self->hold($U->fetch_open_hold_by_copy($self->copy->id)); - $self->checkin_flesh_events; - return; - } - - unless( $self->is_renewal ) { - return $self->bail_on_events($self->editor->event) - unless $self->editor->allowed('COPY_CHECKIN'); - } - - $self->push_events($self->check_copy_alert()); - $self->push_events($self->check_checkin_copy_status()); - - # the renew code will have already found our circulation object - unless( $self->is_renewal and $self->circ ) { - $self->circ( - $self->editor->search_action_circulation( - { target_copy => $self->copy->id, checkin_time => undef })->[0]); - } - - # if the circ is marked as 'claims returned', add the event to the list - $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED')) - if ($self->circ and $self->circ->stop_fines - and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED); - - # handle the overridable events - $self->override_events unless $self->is_renewal; - return if $self->bail_out; - - if( $self->copy ) { - $self->transit( - $self->editor->search_action_transit_copy( - { target_copy => $self->copy->id, dest_recv_time => undef })->[0]); - } - - if( $self->circ ) { - $self->checkin_handle_circ; - return if $self->bail_out; - $self->checkin_changed(1); - - } elsif( $self->transit ) { - my $hold_transit = $self->process_received_transit; - $self->checkin_changed(1); - - if( $self->bail_out ) { - $self->checkin_flesh_events; - return; - } - - if( my $e = $self->check_checkin_copy_status() ) { - # If the original copy status is special, alert the caller - my $ev = $self->events; - $self->events([$e]); - $self->override_events; - return if $self->bail_out; - $self->events($ev); - } - - if( $hold_transit or - $U->copy_status($self->copy->status)->id - == OILS_COPY_STATUS_ON_HOLDS_SHELF ) { - $self->hold( - ($hold_transit) ? - $self->editor->retrieve_action_hold_request($hold_transit->hold) : - $U->fetch_open_hold_by_copy($self->copy->id) - ); - - $self->checkin_flesh_events; - return; - } - - } elsif( $U->copy_status($self->copy->status)->id == OILS_COPY_STATUS_IN_TRANSIT ) { - $logger->warn("circulator: we have a copy ".$self->copy->barcode. - " that is in-transit, but there is no transit.. repairing"); - $self->reshelve_copy(1); - return if $self->bail_out; - } - - if( $self->is_renewal ) { - $self->push_events(OpenILS::Event->new('SUCCESS')); - return; - } + my $self = shift; + $self->log_me("do_checkin()"); + + + return $self->bail_on_events( + OpenILS::Event->new('ASSET_COPY_NOT_FOUND')) + unless $self->copy; + + if( $self->checkin_check_holds_shelf() ) { + $self->bail_on_events(OpenILS::Event->new('NO_CHANGE')); + $self->hold($U->fetch_open_hold_by_copy($self->copy->id)); + $self->checkin_flesh_events; + return; + } + + unless( $self->is_renewal ) { + return $self->bail_on_events($self->editor->event) + unless $self->editor->allowed('COPY_CHECKIN'); + } + + $self->push_events($self->check_copy_alert()); + $self->push_events($self->check_checkin_copy_status()); + + # the renew code will have already found our circulation object + unless( $self->is_renewal and $self->circ ) { + my $circs = $self->editor->search_action_circulation( + { target_copy => $self->copy->id, checkin_time => undef }); + $self->circ($$circs[0]); + + # for now, just warn if there are multiple open circs on a copy + $logger->warn("circulator: we have ".scalar(@$circs). + " open circs for copy " .$self->copy->id."!!") if @$circs > 1; + } + + # if the circ is marked as 'claims returned', add the event to the list + $self->push_events(OpenILS::Event->new('CIRC_CLAIMS_RETURNED')) + if ($self->circ and $self->circ->stop_fines + and $self->circ->stop_fines eq OILS_STOP_FINES_CLAIMSRETURNED); + + # handle the overridable events + $self->override_events unless $self->is_renewal; + return if $self->bail_out; + + if( $self->copy ) { + $self->transit( + $self->editor->search_action_transit_copy( + { target_copy => $self->copy->id, dest_recv_time => undef })->[0]); + } + + if( $self->circ ) { + $self->checkin_handle_circ; + return if $self->bail_out; + $self->checkin_changed(1); + + } elsif( $self->transit ) { + my $hold_transit = $self->process_received_transit; + $self->checkin_changed(1); + + if( $self->bail_out ) { + $self->checkin_flesh_events; + return; + } + + if( my $e = $self->check_checkin_copy_status() ) { + # If the original copy status is special, alert the caller + my $ev = $self->events; + $self->events([$e]); + $self->override_events; + return if $self->bail_out; + $self->events($ev); + } + + if( $hold_transit or + $U->copy_status($self->copy->status)->id + == OILS_COPY_STATUS_ON_HOLDS_SHELF ) { + + my $hold; + if( $hold_transit ) { + $hold = $self->editor->retrieve_action_hold_request($hold_transit->hold); + } else { + ($hold) = $U->fetch_open_hold_by_copy($self->copy->id); + } + + $self->hold($hold); + + if( $hold and $hold->cancel_time ) { # this transited hold was cancelled mid-transit + + $logger->info("circulator: we received a transit on a cancelled hold " . $hold->id); + $self->reshelve_copy(1); + $self->cancelled_hold_transit(1); + $self->notify_hold(0); # don't notify for cancelled holds + return if $self->bail_out; + + } else { + + # hold transited to correct location + $self->checkin_flesh_events; + return; + } + } + + } elsif( $U->copy_status($self->copy->status)->id == OILS_COPY_STATUS_IN_TRANSIT ) { + + $logger->warn("circulator: we have a copy ".$self->copy->barcode. + " that is in-transit, but there is no transit.. repairing"); + $self->reshelve_copy(1); + return if $self->bail_out; + } + + if( $self->is_renewal ) { + $self->push_events(OpenILS::Event->new('SUCCESS')); + return; + } # ------------------------------------------------------------------------------ # Circulations and transits are now closed where necessary. Now go on to see if # this copy can fulfill a hold or needs to be routed to a different location # ------------------------------------------------------------------------------ - if( !$self->remote_hold and $self->attempt_checkin_hold_capture() ) { - return if $self->bail_out; + if( !$self->remote_hold and $self->attempt_checkin_hold_capture() ) { + return if $self->bail_out; } else { # not needed for a hold + my $circ_lib = (ref $self->copy->circ_lib) ? + $self->copy->circ_lib->id : $self->copy->circ_lib; - my $circ_lib = (ref $self->copy->circ_lib) ? - $self->copy->circ_lib->id : $self->copy->circ_lib; - - if( $self->remote_hold ) { - $circ_lib = $self->remote_hold->pickup_lib; - $logger->warn("circulator: Copy ".$self->copy->barcode. - " is on a remote hold's shelf, sending to $circ_lib"); - } + if( $self->remote_hold ) { + $circ_lib = $self->remote_hold->pickup_lib; + $logger->warn("circulator: Copy ".$self->copy->barcode. + " is on a remote hold's shelf, sending to $circ_lib"); + } - $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou); + $logger->debug("circulator: circlib=$circ_lib, workstation=".$self->editor->requestor->ws_ou); if( $circ_lib == $self->editor->requestor->ws_ou ) { - $self->checkin_handle_precat(); - return if $self->bail_out; + $self->checkin_handle_precat(); + return if $self->bail_out; } else { - my $bc = $self->copy->barcode; - $logger->info("circulator: copy $bc at the wrong location, sending to $circ_lib"); - $self->checkin_build_copy_transit($circ_lib); - return if $self->bail_out; - $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib)); + my $bc = $self->copy->barcode; + $logger->info("circulator: copy $bc at the wrong location, sending to $circ_lib"); + $self->checkin_build_copy_transit($circ_lib); + return if $self->bail_out; + $self->push_events(OpenILS::Event->new('ROUTE_ITEM', org => $circ_lib)); } } - $self->reshelve_copy; - return if $self->bail_out; + $self->reshelve_copy; + return if $self->bail_out; - unless($self->checkin_changed) { + unless($self->checkin_changed) { - $self->push_events(OpenILS::Event->new('NO_CHANGE')); - my $stat = $U->copy_status($self->copy->status)->id; + $self->push_events(OpenILS::Event->new('NO_CHANGE')); + my $stat = $U->copy_status($self->copy->status)->id; - $self->hold($U->fetch_open_hold_by_copy($self->copy->id)) + $self->hold($U->fetch_open_hold_by_copy($self->copy->id)) if( $stat == OILS_COPY_STATUS_ON_HOLDS_SHELF ); - $self->bail_out(1); # no need to commit anything + $self->bail_out(1); # no need to commit anything - } else { - $self->push_events(OpenILS::Event->new('SUCCESS')) - unless @{$self->events}; - } + } else { + + $self->push_events(OpenILS::Event->new('SUCCESS')) + unless @{$self->events}; + } # ------------------------------------------------------------------------------ @@ -1393,8 +1426,8 @@ sub do_checkin { patron => $self->patron, background => 1 ) if $self->is_checkin; - $self->checkin_flesh_events; - return; + $self->checkin_flesh_events; + return; } sub reshelve_copy { @@ -1410,10 +1443,10 @@ sub reshelve_copy { $stat != OILS_COPY_STATUS_IN_TRANSIT and $stat != OILS_COPY_STATUS_RESHELVING )) { - $copy->status( OILS_COPY_STATUS_RESHELVING ); - $self->update_copy; - $self->checkin_changed(1); - } + $copy->status( OILS_COPY_STATUS_RESHELVING ); + $self->update_copy; + $self->checkin_changed(1); + } } @@ -1421,66 +1454,66 @@ sub reshelve_copy { # because it was transited there for a hold and the # hold has not been fulfilled sub checkin_check_holds_shelf { - my $self = shift; - return 0 unless $self->copy; - - return 0 unless - $U->copy_status($self->copy->status)->id == - OILS_COPY_STATUS_ON_HOLDS_SHELF; - - # find the hold that put us on the holds shelf - my $holds = $self->editor->search_action_hold_request( - { - current_copy => $self->copy->id, - capture_time => { '!=' => undef }, - fulfillment_time => undef, - cancel_time => undef, - } - ); - - unless(@$holds) { - $logger->warn("circulator: copy is on-holds-shelf, but there is no hold - reshelving"); - $self->reshelve_copy(1); - return 0; - } - - my $hold = $$holds[0]; - - $logger->info("circulator: we found a captured, un-fulfilled hold [". - $hold->id. "] for copy ".$self->copy->barcode); - - if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) { - $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode); - return 1; - } - - $logger->info("circulator: hold is not for here.."); - $self->remote_hold($hold); - return 0; + my $self = shift; + return 0 unless $self->copy; + + return 0 unless + $U->copy_status($self->copy->status)->id == + OILS_COPY_STATUS_ON_HOLDS_SHELF; + + # find the hold that put us on the holds shelf + my $holds = $self->editor->search_action_hold_request( + { + current_copy => $self->copy->id, + capture_time => { '!=' => undef }, + fulfillment_time => undef, + cancel_time => undef, + } + ); + + unless(@$holds) { + $logger->warn("circulator: copy is on-holds-shelf, but there is no hold - reshelving"); + $self->reshelve_copy(1); + return 0; + } + + my $hold = $$holds[0]; + + $logger->info("circulator: we found a captured, un-fulfilled hold [". + $hold->id. "] for copy ".$self->copy->barcode); + + if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) { + $logger->info("circulator: hold is for here .. we're done: ".$self->copy->barcode); + return 1; + } + + $logger->info("circulator: hold is not for here.."); + $self->remote_hold($hold); + return 0; } sub checkin_handle_precat { - my $self = shift; + my $self = shift; my $copy = $self->copy; if( $self->is_precat and ($copy->status != OILS_COPY_STATUS_CATALOGING) ) { $copy->status(OILS_COPY_STATUS_CATALOGING); - $self->update_copy(); - $self->checkin_changed(1); - $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED')); + $self->update_copy(); + $self->checkin_changed(1); + $self->push_events(OpenILS::Event->new('ITEM_NOT_CATALOGED')); } } sub checkin_build_copy_transit { - my $self = shift; - my $dest = shift; - my $copy = $self->copy; + my $self = shift; + my $dest = shift; + my $copy = $self->copy; my $transit = Fieldmapper::action::transit_copy->new; - #$dest ||= (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib; - $logger->info("circulator: transiting copy to $dest"); + #$dest ||= (ref($copy->circ_lib)) ? $copy->circ_lib->id : $copy->circ_lib; + $logger->info("circulator: transiting copy to $dest"); $transit->source($self->editor->requestor->ws_ou); $transit->dest($dest); @@ -1488,116 +1521,118 @@ sub checkin_build_copy_transit { $transit->source_send_time('now'); $transit->copy_status( $U->copy_status($copy->status)->id ); - $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status); + $logger->debug("circulator: setting copy status on transit: ".$transit->copy_status); - return $self->bail_on_events($self->editor->event) - unless $self->editor->create_action_transit_copy($transit); + return $self->bail_on_events($self->editor->event) + unless $self->editor->create_action_transit_copy($transit); $copy->status(OILS_COPY_STATUS_IN_TRANSIT); - $self->update_copy; - $self->checkin_changed(1); + $self->update_copy; + $self->checkin_changed(1); } sub attempt_checkin_hold_capture { - my $self = shift; - my $copy = $self->copy; - - # See if this copy can fulfill any holds - my ($hold) = $holdcode->find_nearest_permitted_hold( - OpenSRF::AppSession->create('open-ils.storage'), - $copy, $self->editor->requestor ); - - if(!$hold) { - $logger->debug("circulator: no potential permitted". - "holds found for copy ".$copy->barcode); - return undef; - } - - - $logger->info("circulator: found permitted hold ". - $hold->id . " for copy, capturing..."); - - $hold->current_copy($copy->id); - $hold->capture_time('now'); - - # prevent DB errors caused by fetching - # holds from storage, and updating through cstore - $hold->clear_fulfillment_time; - $hold->clear_fulfillment_staff; - $hold->clear_fulfillment_lib; - $hold->clear_expire_time; - $hold->clear_cancel_time; - $hold->clear_prev_check_time unless $hold->prev_check_time; - - $self->bail_on_events($self->editor->event) - unless $self->editor->update_action_hold_request($hold); - $self->hold($hold); - $self->checkin_changed(1); - - return 1 if $self->bail_out; - - if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) { - - # This hold was captured in the correct location - $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); - $self->push_events(OpenILS::Event->new('SUCCESS')); - - #$self->do_hold_notify($hold->id); - $self->notify_hold($hold->id); - - } else { - - # Hold needs to be picked up elsewhere. Build a hold - # transit and route the item. - $self->checkin_build_hold_transit(); - $copy->status(OILS_COPY_STATUS_IN_TRANSIT); - return 1 if $self->bail_out; - $self->push_events( - OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib)); - } - - # make sure we save the copy status - $self->update_copy; - return 1; + my $self = shift; + my $copy = $self->copy; + + # See if this copy can fulfill any holds + my ($hold) = $holdcode->find_nearest_permitted_hold( + $self->editor, $copy, $self->editor->requestor ); + + if(!$hold) { + $logger->debug("circulator: no potential permitted". + "holds found for copy ".$copy->barcode); + return undef; + } + + + $logger->info("circulator: found permitted hold ". + $hold->id . " for copy, capturing..."); + + $hold->current_copy($copy->id); + $hold->capture_time('now'); + + # prevent DB errors caused by fetching + # holds from storage, and updating through cstore + $hold->clear_fulfillment_time; + $hold->clear_fulfillment_staff; + $hold->clear_fulfillment_lib; + $hold->clear_expire_time; + $hold->clear_cancel_time; + $hold->clear_prev_check_time unless $hold->prev_check_time; + + $self->bail_on_events($self->editor->event) + unless $self->editor->update_action_hold_request($hold); + $self->hold($hold); + $self->checkin_changed(1); + + return 1 if $self->bail_out; + + if( $hold->pickup_lib == $self->editor->requestor->ws_ou ) { + + # This hold was captured in the correct location + $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); + $self->push_events(OpenILS::Event->new('SUCCESS')); + + #$self->do_hold_notify($hold->id); + $self->notify_hold($hold->id); + + } else { + + # Hold needs to be picked up elsewhere. Build a hold + # transit and route the item. + $self->checkin_build_hold_transit(); + $copy->status(OILS_COPY_STATUS_IN_TRANSIT); + return 1 if $self->bail_out; + $self->push_events( + OpenILS::Event->new('ROUTE_ITEM', org => $hold->pickup_lib)); + } + + # make sure we save the copy status + $self->update_copy; + return 1; } sub do_hold_notify { - my( $self, $holdid ) = @_; + my( $self, $holdid ) = @_; + + $logger->info("circulator: running delayed hold notify process"); - $logger->info("circulator: running delayed hold notify process"); +# my $notifier = OpenILS::Application::Circ::HoldNotify->new( +# hold_id => $holdid, editor => new_editor(requestor=>$self->editor->requestor)); - my $notifier = OpenILS::Application::Circ::HoldNotify->new( - hold_id => $holdid, editor => new_editor(requestor=>$self->editor->requestor)); + my $notifier = OpenILS::Application::Circ::HoldNotify->new( + hold_id => $holdid, requestor => $self->editor->requestor); - $logger->debug("circulator: built hold notifier"); + $logger->debug("circulator: built hold notifier"); - if(!$notifier->event) { + if(!$notifier->event) { - $logger->info("ciculator: attempt at sending hold notification for hold $holdid"); + $logger->info("ciculator: attempt at sending hold notification for hold $holdid"); - my $stat = $notifier->send_email_notify; - if( $stat == '1' ) { - $logger->info("ciculator: hold notify succeeded for hold $holdid"); - return; - } + my $stat = $notifier->send_email_notify; + if( $stat == '1' ) { + $logger->info("ciculator: hold notify succeeded for hold $holdid"); + return; + } - $logger->warn("ciculator: * hold notify failed for hold $holdid"); + $logger->warn("ciculator: * hold notify failed for hold $holdid"); - } else { - $logger->info("ciculator: Not sending hold notification since the patron has no email address"); - } + } else { + $logger->info("ciculator: Not sending hold notification since the patron has no email address"); + } } sub checkin_build_hold_transit { - my $self = shift; + my $self = shift; my $copy = $self->copy; my $hold = $self->hold; my $trans = Fieldmapper::action::hold_transit_copy->new; - $logger->debug("circulator: building hold transit for ".$copy->barcode); + $logger->debug("circulator: building hold transit for ".$copy->barcode); $trans->hold($hold->id); $trans->source($self->editor->requestor->ws_ou); @@ -1605,69 +1640,79 @@ sub checkin_build_hold_transit { $trans->source_send_time("now"); $trans->target_copy($copy->id); - # when the copy gets to its destination, it will recover - # this status - put it onto the holds shelf + # when the copy gets to its destination, it will recover + # this status - put it onto the holds shelf $trans->copy_status(OILS_COPY_STATUS_ON_HOLDS_SHELF); - return $self->bail_on_events($self->editor->event) - unless $self->editor->create_action_hold_transit_copy($trans); + return $self->bail_on_events($self->editor->event) + unless $self->editor->create_action_hold_transit_copy($trans); } sub process_received_transit { - my $self = shift; - my $copy = $self->copy; - my $copyid = $self->copy->id; + my $self = shift; + my $copy = $self->copy; + my $copyid = $self->copy->id; - my $status_name = $U->copy_status($copy->status)->name; - $logger->debug("circulator: attempting transit receive on ". - "copy $copyid. Copy status is $status_name"); + my $status_name = $U->copy_status($copy->status)->name; + $logger->debug("circulator: attempting transit receive on ". + "copy $copyid. Copy status is $status_name"); - my $transit = $self->transit; + my $transit = $self->transit; - if( $transit->dest != $self->editor->requestor->ws_ou ) { - my $tid = $transit->id; - $logger->info("circulator: Fowarding transit on copy which is destined ". - "for a different location. transit=$tid, copy=$copyid,current ". - "location=".$self->editor->requestor->ws_ou.",destination location=".$transit->dest); + if( $transit->dest != $self->editor->requestor->ws_ou ) { + # - this item is in-transit to a different location - return $self->bail_on_events( - OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest )); - } + my $tid = $transit->id; + my $loc = $self->editor->requestor->ws_ou; + my $dest = $transit->dest; + + $logger->info("circulator: Fowarding transit on copy which is destined ". + "for a different location. transit=$tid, copy=$copyid, current ". + "location=$loc, destination location=$dest"); + + my $evt = OpenILS::Event->new('ROUTE_ITEM', org => $dest, payload => {}); + + # grab the associated hold object if available + my $ht = $self->editor->retrieve_action_hold_transit_copy($tid); + $self->hold($self->editor->retrieve_action_hold_request($ht->hold)) if $ht; + + return $self->bail_on_events($evt); + } # The transit is received, set the receive time $transit->dest_recv_time('now'); - $self->bail_on_events($self->editor->event) - unless $self->editor->update_action_transit_copy($transit); + $self->bail_on_events($self->editor->event) + unless $self->editor->update_action_transit_copy($transit); - my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id); + my $hold_transit = $self->editor->retrieve_action_hold_transit_copy($transit->id); - $logger->info("ciculator: Recovering original copy status in transit: ".$transit->copy_status); + $logger->info("circulator: Recovering original copy status in transit: ".$transit->copy_status); $copy->status( $transit->copy_status ); - $self->update_copy(); - return if $self->bail_out; - - my $ishold = 0; - if($hold_transit) { - #$self->do_hold_notify($hold_transit->hold); - $self->notify_hold($hold_transit->hold); - $ishold = 1; - } - - $self->push_events( - OpenILS::Event->new( - 'SUCCESS', - ishold => $ishold, + $self->update_copy(); + return if $self->bail_out; + + my $ishold = 0; + if($hold_transit) { + #$self->do_hold_notify($hold_transit->hold); + $self->notify_hold($hold_transit->hold); + $ishold = 1; + } + + $self->push_events( + OpenILS::Event->new( + 'SUCCESS', + ishold => $ishold, payload => { transit => $transit, holdtransit => $hold_transit } )); - return $hold_transit; + return $hold_transit; } sub checkin_handle_circ { my $self = shift; - $U->logmark; + $U->logmark; my $circ = $self->circ; my $copy = $self->copy; @@ -1676,8 +1721,8 @@ sub checkin_handle_circ { # backdate the circ if necessary if($self->backdate) { - $self->checkin_handle_backdate; - return if $self->bail_out; + $self->checkin_handle_backdate; + return if $self->bail_out; } if(!$circ->stop_fines) { @@ -1688,67 +1733,72 @@ sub checkin_handle_circ { } # see if there are any fines owed on this circ. if not, close it - ($obt) = $U->fetch_mbts($circ->id, $self->editor); + ($obt) = $U->fetch_mbts($circ->id, $self->editor); $circ->xact_finish('now') if( $obt and $obt->balance_owed == 0 ); - $logger->debug("circulator: ".$obt->balance_owed." is owed on this circulation"); + $logger->debug("circulator: ".$obt->balance_owed." is owed on this circulation"); # Set the checkin vars since we have the item - $circ->checkin_time('now'); + $circ->checkin_time( ($self->backdate) ? $self->backdate : 'now' ); + $circ->checkin_staff($self->editor->requestor->id); $circ->checkin_lib($self->editor->requestor->ws_ou); - my $circ_lib = (ref $self->copy->circ_lib) ? - $self->copy->circ_lib->id : $self->copy->circ_lib; - my $stat = $U->copy_status($self->copy->status)->id; - - # If the item is lost/missing and it needs to be sent home, don't - # reshelve the copy, leave it lost/missing so the recipient will know - if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING) - and ($circ_lib != $self->editor->requestor->ws_ou) ) { - $logger->info("circulator: not updating copy status on checkin because copy is lost/missing"); + my $circ_lib = (ref $self->copy->circ_lib) ? + $self->copy->circ_lib->id : $self->copy->circ_lib; + my $stat = $U->copy_status($self->copy->status)->id; - } else { - $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING)); - $self->update_copy; - } + # If the item is lost/missing and it needs to be sent home, don't + # reshelve the copy, leave it lost/missing so the recipient will know + if( ($stat == OILS_COPY_STATUS_LOST or $stat == OILS_COPY_STATUS_MISSING) + and ($circ_lib != $self->editor->requestor->ws_ou) ) { + $logger->info("circulator: not updating copy status on checkin because copy is lost/missing"); + } else { + $self->copy->status($U->copy_status(OILS_COPY_STATUS_RESHELVING)); + $self->update_copy; + } - return $self->bail_on_events($self->editor->event) - unless $self->editor->update_action_circulation($circ); + return $self->bail_on_events($self->editor->event) + unless $self->editor->update_action_circulation($circ); } sub checkin_handle_backdate { - my $self = shift; - - my $bd = $self->backdate; - $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og; - $bd = "${bd}T23:59:59"; - - my $bills = $self->editor->search_money_billing( - { - billing_ts => { '>=' => $bd }, - xact => $self->circ->id, - billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS - } - ); - - $logger->debug("backdate found ".scalar(@$bills)." bills to void"); - - for my $bill (@$bills) { - unless( $U->is_true($bill->voided) ) { - $logger->info("backdate voiding bill ".$bill->id); - $bill->voided('t'); - $bill->void_time('now'); - $bill->voider($self->editor->requestor->id); - my $n = $bill->note || ""; - $bill->note("$n\nSystem: VOIDED FOR BACKDATE"); - - $self->bail_on_events($self->editor->event) - unless $self->editor->update_money_billing($bill); - } - } + my $self = shift; + + my $bd = $self->backdate; + + # ------------------------------------------------------------------ + # clean up the backdate for date comparison + # we want any bills created on or after the backdate + # ------------------------------------------------------------------ + $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og; + #$bd = "${bd}T23:59:59"; + + my $bills = $self->editor->search_money_billing( + { + billing_ts => { '>=' => $bd }, + xact => $self->circ->id, + billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS + } + ); + + $logger->debug("backdate found ".scalar(@$bills)." bills to void"); + + for my $bill (@$bills) { + unless( $U->is_true($bill->voided) ) { + $logger->info("backdate voiding bill ".$bill->id); + $bill->voided('t'); + $bill->void_time('now'); + $bill->voider($self->editor->requestor->id); + my $n = $bill->note || ""; + $bill->note("$n\nSystem: VOIDED FOR BACKDATE"); + + $self->bail_on_events($self->editor->event) + unless $self->editor->update_money_billing($bill); + } + } } @@ -1758,36 +1808,36 @@ sub checkin_handle_backdate { sub _checkin_handle_backdate { my( $class, $backdate, $circ, $requestor, $session, $closecirc ) = @_; - my $bd = $backdate; - $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og; - $bd = "${bd}T23:59:59"; + my $bd = $backdate; + $bd =~ s/^(\d{4}-\d{2}-\d{2}).*/$1/og; + $bd = "${bd}T23:59:59"; my $bills = $session->request( "open-ils.storage.direct.money.billing.search_where.atomic", - billing_ts => { '>=' => $bd }, - xact => $circ->id, - billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS - )->gather(1); + billing_ts => { '>=' => $bd }, + xact => $circ->id, + billing_type => OILS_BILLING_TYPE_OVERDUE_MATERIALS + )->gather(1); - $logger->debug("backdate found ".scalar(@$bills)." bills to void"); + $logger->debug("backdate found ".scalar(@$bills)." bills to void"); if($bills) { for my $bill (@$bills) { - unless( $U->is_true($bill->voided) ) { - $logger->debug("voiding bill ".$bill->id); - $bill->voided('t'); - $bill->void_time('now'); - $bill->voider($requestor->id); - my $n = $bill->note || ""; - $bill->note($n . "\nSystem: VOIDED FOR BACKDATE"); - my $s = $session->request( - "open-ils.storage.direct.money.billing.update", $bill)->gather(1); - return $U->DB_UPDATE_FAILED($bill) unless $s; - } - } + unless( $U->is_true($bill->voided) ) { + $logger->debug("voiding bill ".$bill->id); + $bill->voided('t'); + $bill->void_time('now'); + $bill->voider($requestor->id); + my $n = $bill->note || ""; + $bill->note($n . "\nSystem: VOIDED FOR BACKDATE"); + my $s = $session->request( + "open-ils.storage.direct.money.billing.update", $bill)->gather(1); + return $U->DB_UPDATE_FAILED($bill) unless $s; + } + } } - return 100; + return 100; } =cut @@ -1797,18 +1847,18 @@ sub _checkin_handle_backdate { sub find_patron_from_copy { - my $self = shift; - my $circs = $self->editor->search_action_circulation( - { target_copy => $self->copy->id, checkin_time => undef }); - my $circ = $circs->[0]; - return unless $circ; - my $u = $self->editor->retrieve_actor_user($circ->usr) - or return $self->bail_on_events($self->editor->event); - $self->patron($u); + my $self = shift; + my $circs = $self->editor->search_action_circulation( + { target_copy => $self->copy->id, checkin_time => undef }); + my $circ = $circs->[0]; + return unless $circ; + my $u = $self->editor->retrieve_actor_user($circ->usr) + or return $self->bail_on_events($self->editor->event); + $self->patron($u); } sub check_checkin_copy_status { - my $self = shift; + my $self = shift; my $copy = $self->copy; my $islost = 0; @@ -1841,127 +1891,131 @@ sub check_checkin_copy_status { # On checkin, we need to return as many relevant objects as we can # -------------------------------------------------------------------------- sub checkin_flesh_events { - my $self = shift; + my $self = shift; + + if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events} + and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) { + $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]); + } + - if( grep { $_->{textcode} eq 'SUCCESS' } @{$self->events} - and grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events} ) { - $self->events([grep { $_->{textcode} eq 'ITEM_NOT_CATALOGED' } @{$self->events}]); - } + for my $evt (@{$self->events}) { + my $payload = {}; + $payload->{copy} = $U->unflesh_copy($self->copy); + $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat); + $payload->{circ} = $self->circ; + $payload->{transit} = $self->transit; + $payload->{cancelled_hold_transit} = 1 if $self->cancelled_hold_transit; - for my $evt (@{$self->events}) { + # $self->hold may or may not have been replaced with a + # valid hold after processing a cancelled hold + $payload->{hold} = $self->hold unless (not $self->hold or $self->hold->cancel_time); - my $payload = {}; - $payload->{copy} = $U->unflesh_copy($self->copy); - $payload->{record} = $U->record_to_mvr($self->title) if($self->title and !$self->is_precat); - $payload->{circ} = $self->circ; - $payload->{transit} = $self->transit; - $payload->{hold} = $self->hold; - - $evt->{payload} = $payload; - } + $evt->{payload} = $payload; + } } sub log_me { - my( $self, $msg ) = @_; - my $bc = ($self->copy) ? $self->copy->barcode : - $self->barcode; - $bc ||= ""; - my $usr = ($self->patron) ? $self->patron->id : ""; - $logger->info("circulator: $msg requestor=".$self->editor->requestor->id. - ", recipient=$usr, copy=$bc"); + my( $self, $msg ) = @_; + my $bc = ($self->copy) ? $self->copy->barcode : + $self->barcode; + $bc ||= ""; + my $usr = ($self->patron) ? $self->patron->id : ""; + $logger->info("circulator: $msg requestor=".$self->editor->requestor->id. + ", recipient=$usr, copy=$bc"); } sub do_renew { - my $self = shift; - $self->log_me("do_renew()"); - $self->is_renewal(1); - - unless( $self->is_renewal ) { - return $self->bail_on_events($self->editor->events) - unless $self->editor->allowed('RENEW_CIRC'); - } - - # Make sure there is an open circ to renew that is not - # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE - my $circ = $self->editor->search_action_circulation( - { target_copy => $self->copy->id, stop_fines => undef } )->[0]; - - if(!$circ) { - $circ = $self->editor->search_action_circulation( - { - target_copy => $self->copy->id, - stop_fines => OILS_STOP_FINES_MAX_FINES, - checkin_time => undef - } - )->[0]; - } - - return $self->bail_on_events($self->editor->event) unless $circ; - - $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED')) - if $circ->renewal_remaining < 1; - - # ----------------------------------------------------------------- - - $self->renewal_remaining( $circ->renewal_remaining - 1 ); - $self->circ($circ); - - $self->run_renew_permit; - - # Check the item in - $self->do_checkin(); - return if $self->bail_out; - - unless( $self->permit_override ) { - $self->do_permit(); - return if $self->bail_out; - $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED'); - $self->remove_event('ITEM_NOT_CATALOGED'); - } - - $self->override_events; - return if $self->bail_out; - - $self->events([]); - $self->do_checkout(); + my $self = shift; + $self->log_me("do_renew()"); + $self->is_renewal(1); + + unless( $self->is_renewal ) { + return $self->bail_on_events($self->editor->events) + unless $self->editor->allowed('RENEW_CIRC'); + } + + # Make sure there is an open circ to renew that is not + # marked as LOST, CLAIMSRETURNED, or LONGOVERDUE + my $circ = $self->editor->search_action_circulation( + { target_copy => $self->copy->id, stop_fines => undef } )->[0]; + + if(!$circ) { + $circ = $self->editor->search_action_circulation( + { + target_copy => $self->copy->id, + stop_fines => OILS_STOP_FINES_MAX_FINES, + checkin_time => undef + } + )->[0]; + } + + return $self->bail_on_events($self->editor->event) unless $circ; + + $self->push_events(OpenILS::Event->new('MAX_RENEWALS_REACHED')) + if $circ->renewal_remaining < 1; + + # ----------------------------------------------------------------- + + $self->renewal_remaining( $circ->renewal_remaining - 1 ); + $self->circ($circ); + + $self->run_renew_permit; + + # Check the item in + $self->do_checkin(); + return if $self->bail_out; + + unless( $self->permit_override ) { + $self->do_permit(); + return if $self->bail_out; + $self->is_precat(1) if $self->have_event('ITEM_NOT_CATALOGED'); + $self->remove_event('ITEM_NOT_CATALOGED'); + } + + $self->override_events; + return if $self->bail_out; + + $self->events([]); + $self->do_checkout(); } sub remove_event { - my( $self, $evt ) = @_; - $evt = (ref $evt) ? $evt->{textcode} : $evt; - $logger->debug("circulator: removing event from list: $evt"); - my @events = @{$self->events}; - $self->events( [ grep { $_->{textcode} ne $evt } @events ] ); + my( $self, $evt ) = @_; + $evt = (ref $evt) ? $evt->{textcode} : $evt; + $logger->debug("circulator: removing event from list: $evt"); + my @events = @{$self->events}; + $self->events( [ grep { $_->{textcode} ne $evt } @events ] ); } sub have_event { - my( $self, $evt ) = @_; - $evt = (ref $evt) ? $evt->{textcode} : $evt; - return grep { $_->{textcode} eq $evt } @{$self->events}; + my( $self, $evt ) = @_; + $evt = (ref $evt) ? $evt->{textcode} : $evt; + return grep { $_->{textcode} eq $evt } @{$self->events}; } sub run_renew_permit { - my $self = shift; + my $self = shift; my $runner = $self->script_runner; $runner->load($self->circ_permit_renew); my $result = $runner->run or - throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@"); + throw OpenSRF::EX::ERROR ("Circ Permit Renew Script Died: $@"); my $events = $result->{events}; $logger->activity("ciculator: circ_permit_renew for user ". $self->patron->id." returned events: @$events") if @$events; - $self->push_events(OpenILS::Event->new($_)) for @$events; - - $logger->debug("circulator: re-creating script runner to be safe"); - $self->mk_script_runner; + $self->push_events(OpenILS::Event->new($_)) for @$events; + + $logger->debug("circulator: re-creating script runner to be safe"); + $self->mk_script_runner; } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/CopyLocations.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/CopyLocations.pm index 600fe0762d..ab572bfa03 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/CopyLocations.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/CopyLocations.pm @@ -48,25 +48,27 @@ __PACKAGE__->register_method( @return The if of the new location object on success, event on error /); + sub cl_create { - my( $self, $client, $authtoken, $copyLoc ) = @_; - my( $requestor, $evt ) = $U->checkses($authtoken); - return $evt if $evt; - $evt = $U->check_perms($requestor->id, - $copyLoc->owning_lib, 'CREATE_COPY_LOCATION'); - return $evt if $evt; - - my $cl; - ($cl, $evt) = $U->fetch_copy_location_by_name($copyLoc->name, $copyLoc->owning_lib); - return OpenILS::Event->new('COPY_LOCATION_EXISTS') if $cl; - - my $id = $U->storagereq( - 'open-ils.storage.direct.asset.copy_location.create', $copyLoc ); - - return $U->DB_UPDATE_FAILED($copyLoc) unless $id; - return $id; + my( $self, $conn, $auth, $location ) = @_; + + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + return $e->die_event unless + $e->allowed('CREATE_COPY_LOCATION', $location->owning_lib); + + # make sure there is no copy_location with the same name in the same place + my $existing = $e->search_asset_copy_location( + {owning_lib => $location->owning_lib, name => $location->name}, {idlist=>1}); + return OpenILS::Event->new('COPY_LOCATION_EXISTS') if @$existing; + + $e->create_asset_copy_location($location) or return $e->die_event; + $e->commit; + return $location->id; } + + __PACKAGE__->register_method ( api_name => 'open-ils.circ.copy_location.delete', method => 'cl_delete', @@ -79,26 +81,24 @@ __PACKAGE__->register_method ( @return 1 on success, event on error /); + sub cl_delete { - my( $self, $client, $authtoken, $id ) = @_; - my( $requestor, $evt ) = $U->checkses($authtoken); - return $evt if $evt; - - my $cl; - ($cl, $evt) = $U->fetch_copy_location($id); - return $evt if $evt; - - $evt = $U->check_perms($requestor->id, - $cl->owning_lib, 'DELETE_COPY_LOCATION'); - return $evt if $evt; - - my $resp = $U->storagereq( - 'open-ils.storage.direct.asset.copy_location.delete', $id ); - - return $U->DB_UPDATE_FAILED unless $resp; - return 1; + my( $self, $conn, $auth, $id ) = @_; + + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + + my $cloc = $e->retrieve_asset_copy_location($id) + or return $e->die_event; + return $e->die_event unless + $e->allowed('DELETE_COPY_LOCATION', $cloc->owning_lib); + + $e->delete_asset_copy_location($cloc) or return $e->die_event; + $e->commit; + return 1; } + __PACKAGE__->register_method ( api_name => 'open-ils.circ.copy_location.update', method => 'cl_update', @@ -111,29 +111,32 @@ __PACKAGE__->register_method ( @return 1 on success, event on error /); + sub cl_update { - my( $self, $client, $authtoken, $copyLoc ) = @_; + my( $self, $conn, $auth, $location ) = @_; + + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; - my( $requestor, $evt ) = $U->checkses($authtoken); - return $evt if $evt; + # check permissions against the original copy location + my $orig_loc = $e->retrieve_asset_copy_location($location->id) + or return $e->die_event; - my $cl; - ($cl, $evt) = $U->fetch_copy_location($copyLoc->id); - return $evt if $evt; + return $e->die_event unless + $e->allowed('UPDATE_COPY_LOCATION', $orig_loc->owning_lib); - $evt = $U->check_perms($requestor->id, - $cl->owning_lib, 'UPDATE_COPY_LOCATION'); - return $evt if $evt; + # disallow hijacking of the location + $location->owning_lib($orig_loc->owning_lib); - $copyLoc->owning_lib($cl->owning_lib); #disallow changing of the lib + $e->update_asset_copy_location($location) + or return $e->die_event; - my $resp = $U->storagereq( - 'open-ils.storage.direct.asset.copy_location.update', $copyLoc ); - - return 1; # if there was no exception thrown, then the update was a success + $e->commit; + return 1; } + __PACKAGE__->register_method( method => 'fetch_loc', api_name => 'open-ils.circ.copy_location.retrieve', diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/HoldNotify.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/HoldNotify.pm index 75b7ab14c1..3f35826fbb 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/HoldNotify.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/HoldNotify.pm @@ -45,7 +45,7 @@ sub send_email_notify_pub { my $e = new_editor(authtoken => $auth); return $e->event unless $e->checkauth; return $e->event unless $e->allowed('CREATE_HOLD_NOTIFICATION'); - my $notifier = __PACKAGE__->new(editor=> $e, hold_id => $hold_id); + my $notifier = __PACKAGE__->new(requestor => $e->requestor, hold_id => $hold_id); return $notifier->event if $notifier->event; my $stat = $notifier->send_email_notify; # $e->commit if $stat == '1'; @@ -102,14 +102,13 @@ sub new { my( $class, %args ) = @_; $class = ref($class) || $class; my $self = bless( {}, $class ); - $self->editor($args{editor}); + $self->editor( new_editor( xact => 1, requestor => $args{requestor} )); $logger->debug("circulator: creating new hold-notifier with requestor ". $self->editor->requestor->id); $self->fetch_data($args{hold_id}); return $self; } - sub send_email_notify { my $self = shift; @@ -120,18 +119,21 @@ sub send_email_notify { $logger->debug("hold_notify: email enabled setting = $setting"); if( !$setting or $setting ne 'true' ) { + $self->editor->rollback; $logger->info("hold_notify: not sending hold notify - email notifications disabled"); return 0; } unless ($U->is_true($self->hold->email_notify)) { + $self->editor->rollback; $logger->info("hold_notify: not sending hold notification becaue email_notify is false"); return 0; } - return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS') - unless $self->patron->email and - $self->patron->email =~ /.+\@.+/; # see if it's remotely email-esque + unless( $self->patron->email and $self->patron->email =~ /.+\@.+/ ) { # see if it's remotely email-esque + $self->editor->rollback; + return OpenILS::Event->new('PATRON_NO_EMAIL_ADDRESS'); + } $logger->info("hold_notify: attempting email notify on hold ".$self->hold->id); @@ -141,10 +143,14 @@ sub send_email_notify { my $str = $self->flesh_template($self->load_template($template)); unless( $str ) { + $self->editor->rollback; $logger->error("hold_notify: No email notifiy template found - cannot notify"); return 0; } + my $reqr = $self->editor->requestor; + $self->editor->rollback; # we're done with this transaction + return 0 unless $self->send_email($str); # ------------------------------------------------------------------ @@ -152,7 +158,7 @@ sub send_email_notify { # transaction may have timed out. Create a one-off editor to write # the notification to the DB. # ------------------------------------------------------------------ - my $we = new_editor(xact=>1, requestor=>$self->editor->requestor); + my $we = new_editor(xact=>1, requestor=>$reqr); my $notify = Fieldmapper::action::hold_notification->new; $notify->hold($self->hold->id); @@ -170,6 +176,8 @@ sub send_email_notify { sub send_email { my( $self, $text ) = @_; + # !!! $self->editor xact has been rolled back before we get here + my $smtp = $self->settings_client->config_value('email_notify', 'smtp_server'); $logger->info("hold_notify: sending email notice to ". @@ -337,6 +345,14 @@ sub flesh_template { my $reply_to = $self->pickup_lib->email; $reply_to ||= $sender; + # if they have an org setting for bounced emails, use that as the sender address + if( my $set = $self->editor->search_actor_org_unit_setting( + { name => OILS_SETTING_ORG_BOUNCED_EMAIL, + org_unit => $self->pickup_lib->id } )->[0] ) { + + my $bemail = JSON->JSON2perl($set->value); + $sender = $bemail if $bemail; + } $str =~ s/\${EMAIL_SENDER}/$sender/; $str =~ s/\${EMAIL_RECIPIENT}/$email/; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm index f67a50c202..9d37096ec7 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Holds.pm @@ -363,6 +363,35 @@ sub retrieve_holds { } } + +__PACKAGE__->register_method( + method => 'user_hold_count', + api_name => 'open-ils.circ.hold.user.count'); + +sub user_hold_count { + my( $self, $conn, $auth, $userid ) = @_; + my $e = new_editor(authtoken=>$auth); + return $e->event unless $e->checkauth; + my $patron = $e->retrieve_actor_user($userid) + or return $e->event; + return $e->event unless $e->allowed('VIEW_HOLD', $patron->home_ou); + return $self->__user_hold_count($e, $userid); +} + +sub __user_hold_count { + my( $self, $e, $userid ) = @_; + my $holds = $e->search_action_hold_request( + { usr => $userid , + fulfillment_time => undef, + cancel_time => undef, + }, + {idlist => 1} + ); + + return scalar(@$holds); +} + + __PACKAGE__->register_method( method => "retrieve_holds_by_pickup_lib", api_name => "open-ils.circ.holds.retrieve_by_pickup_lib", @@ -441,11 +470,12 @@ sub cancel_hold { or return $e->event; if( $copy->status == OILS_COPY_STATUS_ON_HOLDS_SHELF ) { - $logger->info("setting copy to status 'reshelving' on hold cancel"); - $copy->status(OILS_COPY_STATUS_RESHELVING); - $copy->editor($e->requestor->id); - $copy->edit_date('now'); - $e->update_asset_copy($copy) or return $e->event; + $logger->info("canceling hold $holdid whose item is on the holds shelf"); +# $logger->info("setting copy to status 'reshelving' on hold cancel"); +# $copy->status(OILS_COPY_STATUS_RESHELVING); +# $copy->editor($e->requestor->id); +# $copy->edit_date('now'); +# $e->update_asset_copy($copy) or return $e->event; } elsif( $copy->status == OILS_COPY_STATUS_IN_TRANSIT ) { @@ -455,21 +485,13 @@ sub cancel_hold { if( $transid ) { my $trans = $e->retrieve_action_transit_copy($transid); + # Leave the transit alive, but set the copy status to + # reshelving so it will be properly reshelved when it gets back home if( $trans ) { - $logger->info("Aborting transit [$transid] on hold [$hid] cancel..."); - my $evt = OpenILS::Application::Circ::Transit::__abort_transit($e, $trans, $copy, 1); - $logger->info("Transit abort completed with result $evt"); - return $evt unless "$evt" eq 1; + $trans->copy_status( OILS_COPY_STATUS_RESHELVING ); + $e->update_action_transit_copy($trans) or return $e->die_event; } } - - # We don't want the copy to remain "in transit" or to recover - # any previous statuses - $logger->info("setting copy back to reshelving in hold+transit cancel"); - $copy->status(OILS_COPY_STATUS_RESHELVING); - $copy->editor($e->requestor->id); - $copy->edit_date('now'); - $e->update_asset_copy($copy) or return $e->event; } } @@ -569,147 +591,10 @@ sub _hold_status { } - - - -=head DEPRECATED -__PACKAGE__->register_method( - method => "capture_copy", - api_name => "open-ils.circ.hold.capture_copy.barcode", - notes => <<" NOTE"); - Captures a copy to fulfil a hold - Params is login session and copy barcode - Optional param is 'flesh'. If set, we also return the - relevant copy and title - login mus have COPY_CHECKIN permissions (since this is essentially - copy checkin) - NOTE - -# XXX deprecate me XXX - -sub capture_copy { - my( $self, $client, $login_session, $params ) = @_; - my %params = %$params; - my $barcode = $params{barcode}; - - - my( $user, $target, $copy, $hold, $evt ); - - ( $user, $evt ) = $apputils->checkses($login_session); - return $evt if $evt; - - # am I allowed to checkin a copy? - $evt = $apputils->check_perms($user->id, $user->home_ou, "COPY_CHECKIN"); - return $evt if $evt; - - $logger->info("Capturing copy with barcode $barcode"); - - my $session = $apputils->start_db_session(); - - ($copy, $evt) = $apputils->fetch_copy_by_barcode($barcode); - return $evt if $evt; - - $logger->debug("Capturing copy " . $copy->id); - - #( $hold, $evt ) = _find_local_hold_for_copy($session, $copy, $user); - ( $hold, $evt ) = $self->find_nearest_permitted_hold($session, $copy, $user); - return $evt if $evt; - - warn "Found hold " . $hold->id . "\n"; - $logger->info("We found a hold " .$hold->id. "for capturing copy with barcode $barcode"); - - $hold->current_copy($copy->id); - $hold->capture_time("now"); - - #update the hold - my $stat = $session->request( - "open-ils.storage.direct.action.hold_request.update", $hold)->gather(1); - if(!$stat) { throw OpenSRF::EX::ERROR - ("Error updating hold request " . $copy->id); } - - $copy->status(OILS_COPY_STATUS_ON_HOLDS_SHELF); #status on holds shelf - - # if the staff member capturing this item is not at the pickup lib - if( $user->home_ou ne $hold->pickup_lib ) { - $self->_build_hold_transit( $login_session, $session, $hold, $user, $copy ); - } - - $copy->editor($user->id); - $copy->edit_date("now"); - $stat = $session->request( - "open-ils.storage.direct.asset.copy.update", $copy )->gather(1); - if(!$stat) { throw OpenSRF::EX ("Error updating copy " . $copy->id); } - - my $payload = { hold => $hold }; - $payload->{copy} = $copy if $params{flesh_copy}; - - if($params{flesh_record}) { - my $record; - ($record, $evt) = $apputils->fetch_record_by_copy( $copy->id ); - return $evt if $evt; - $record = $apputils->record_to_mvr($record); - $payload->{record} = $record; - } - - $apputils->commit_db_session($session); - - return OpenILS::Event->new('ROUTE_ITEM', - route_to => $hold->pickup_lib, payload => $payload ); -} - -sub _build_hold_transit { - my( $self, $login_session, $session, $hold, $user, $copy ) = @_; - my $trans = Fieldmapper::action::hold_transit_copy->new; - - $trans->hold($hold->id); - $trans->source($user->home_ou); - $trans->dest($hold->pickup_lib); - $trans->source_send_time("now"); - $trans->target_copy($copy->id); - $trans->copy_status($copy->status); - - my $meth = $self->method_lookup("open-ils.circ.hold_transit.create"); - my ($stat) = $meth->run( $login_session, $trans, $session ); - if(!$stat) { throw OpenSRF::EX ("Error creating new hold transit"); } - else { $copy->status(6); } #status in transit -} - - - -__PACKAGE__->register_method( - method => "create_hold_transit", - api_name => "open-ils.circ.hold_transit.create", - notes => <<" NOTE"); - Creates a new transit object - NOTE - -sub create_hold_transit { - my( $self, $client, $login_session, $transit, $session ) = @_; - - my( $user, $evt ) = $apputils->checkses($login_session); - return $evt if $evt; - $evt = $apputils->check_perms($user->id, $user->home_ou, "CREATE_TRANSIT"); - return $evt if $evt; - - my $ses; - if($session) { $ses = $session; } - else { $ses = OpenSRF::AppSession->create("open-ils.storage"); } - - return $ses->request( - "open-ils.storage.direct.action.hold_transit_copy.create", $transit )->gather(1); -} - -=cut - - -sub find_local_hold { - my( $class, $session, $copy, $user ) = @_; - return $class->find_nearest_permitted_hold($session, $copy, $user); -} - - - - +#sub find_local_hold { +# my( $class, $session, $copy, $user ) = @_; +# return $class->find_nearest_permitted_hold($session, $copy, $user); +#} sub fetch_open_hold_by_current_copy { @@ -814,7 +699,8 @@ __PACKAGE__->register_method ( @return ID of the new object on success, Event on error / ); -sub create_hold_notify { +=head old +sub __create_hold_notify { my( $self, $conn, $authtoken, $notification ) = @_; my( $requestor, $evt ) = $U->checkses($authtoken); return $evt if $evt; @@ -836,6 +722,26 @@ sub create_hold_notify { $logger->info("User ".$requestor->id." successfully created new hold notification $id"); return $id; } +=cut + +sub create_hold_notify { + my( $self, $conn, $auth, $note ) = @_; + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + + my $hold = $e->retrieve_action_hold_request($note->hold) + or return $e->die_event; + my $patron = $e->retrieve_actor_user($hold->usr) + or return $e->die_event; + + return $e->die_event unless + $e->allowed('CREATE_HOLD_NOTIFICATION', $patron->home_ou); + + $note->notify_staff($e->requestor->id); + $e->create_action_hold_notification($note) or return $e->die_event; + $e->commit; + return $note->id; +} __PACKAGE__->register_method( @@ -975,7 +881,7 @@ sub flesh_hold_notices { my $notices = $e->search_action_hold_notification( [ { hold => $hold->id }, - { order_by => { anh => { 'notify_time desc' } } }, + { order_by => { anh => 'notify_time desc' } }, ], {idlist=>1} ); @@ -1088,9 +994,7 @@ sub check_title_hold { $e->allowed('VIEW_HOLD_PERMIT', $patron->home_ou); } - return OpenILS::Event->new('PATRON_BARRED') - if $patron->barred and - ($patron->barred =~ /t/i or $patron->barred == 1); + return OpenILS::Event->new('PATRON_BARRED') if $U->is_true($patron->barred); my $rangelib = $params{range_lib} || $patron->home_ou; @@ -1142,7 +1046,7 @@ sub check_title_hold { -sub _check_title_hold_is_possible { +sub ___check_title_hold_is_possible { my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_; my $limit = 10; @@ -1177,6 +1081,120 @@ sub _check_title_hold_is_possible { return 0; } +my %prox_cache; + +sub _check_title_hold_is_possible { + my( $titleid, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_; + + my $e = new_editor(); + + # this monster will grab the id and circ_lib of all of the "holdable" copies for the given record + my $copies = $e->json_query( + { + select => { acp => ['id', 'circ_lib'] }, + from => { + acp => { + acn => { + field => 'id', + fkey => 'call_number', + 'join' => { + bre => { + field => 'id', + filter => { id => $titleid }, + fkey => 'record' + } + } + }, + acpl => { field => 'id', filter => { holdable => 't'}, fkey => 'location' }, + ccs => { field => 'id', filter => { holdable => 't'}, fkey => 'status' } + } + }, + where => { + '+acp' => { circulate => 't', deleted => 'f', holdable => 't' } + } + } + ); + + return $e->event unless defined $copies; + $logger->info("title possible found ".scalar(@$copies)." potential copies"); + return 0 unless @$copies; + + # ----------------------------------------------------------------------- + # sort the copies into buckets based on their circ_lib proximity to + # the patron's home_ou. + # ----------------------------------------------------------------------- + + my $home_org = $patron->home_ou; + my $req_org = $request_lib->id; + + my $home_prox = + ($prox_cache{$home_org}) ? + $prox_cache{$home_org} : + $prox_cache{$home_org} = $e->search_actor_org_unit_proximity({from_org => $home_org}); + + my %buckets; + my %hash = map { ($_->to_org => $_->prox) } @$home_prox; + push( @{$buckets{ $hash{$_->{circ_lib}} } }, $_->{id} ) for @$copies; + + my @keys = sort { $a <=> $b } keys %buckets; + + + if( $home_org ne $req_org ) { + # ----------------------------------------------------------------------- + # shove the copies close to the request_lib into the primary buckets + # directly before the farthest away copies. That way, they are not + # given priority, but they are checked before the farthest copies. + # ----------------------------------------------------------------------- + my $req_prox = + ($prox_cache{$req_org}) ? + $prox_cache{$req_org} : + $prox_cache{$req_org} = $e->search_actor_org_unit_proximity({from_org => $req_org}); + + my %buckets2; + my %hash2 = map { ($_->to_org => $_->prox) } @$req_prox; + push( @{$buckets2{ $hash2{$_->{circ_lib}} } }, $_->{id} ) for @$copies; + + my $highest_key = $keys[@keys - 1]; # the farthest prox in the exising buckets + my $new_key = $highest_key - 0.5; # right before the farthest prox + my @keys2 = sort { $a <=> $b } keys %buckets2; + for my $key (@keys2) { + last if $key >= $highest_key; + push( @{$buckets{$new_key}}, $_ ) for @{$buckets2{$key}}; + } + } + + @keys = sort { $a <=> $b } keys %buckets; + + my $title; + my %seen; + for my $key (@keys) { + my @cps = @{$buckets{$key}}; + + $logger->info("looking at " . scalar(@{$buckets{$key}}). " copies in proximity bucket $key"); + + for my $copyid (@cps) { + + next if $seen{$copyid}; + $seen{$copyid} = 1; # there could be dupes given the merged buckets + my $copy = $e->retrieve_asset_copy($copyid) or return $e->event; + $logger->debug("looking at bucket_key=$key, copy $copyid : circ_lib = " . $copy->circ_lib); + + unless($title) { # grab the title if we don't already have it + my $vol = $e->retrieve_asset_call_number( + [ $copy->call_number, { flesh => 1, flesh_fields => { acn => ['record'] } } ] ); + $title = $vol->record; + } + + return 1 if verify_copy_for_hold( + $patron, $requestor, $title, $copy, $pickup_lib, $request_lib ); + + } + } + + return 0; +} + + sub _check_volume_hold_is_possible { my( $vol, $title, $rangelib, $depth, $request_lib, $patron, $requestor, $pickup_lib ) = @_; my $copies = new_editor->search_asset_copy({call_number => $vol->id}); @@ -1211,58 +1229,106 @@ sub verify_copy_for_hold { sub find_nearest_permitted_hold { my $class = shift; - my $session = shift; - my $copy = shift; - my $user = shift; + my $editor = shift; # CStoreEditor object + my $copy = shift; # copy to target + my $user = shift; # hold recipient + my $check_only = shift; # do no updates, just see if the copy could fulfill a hold my $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND'); - # first see if this copy has already been selected to fulfill a hold - my $hold = $session->request( - "open-ils.storage.direct.action.hold_request.search_where", - { current_copy => $copy->id, cancel_time => undef, capture_time => undef } )->gather(1); + my $bc = $copy->barcode; - if( $hold ) { - $logger->info("hold found which can be fulfilled by copy ".$copy->id); - return $hold; - } + # find any existing holds that already target this copy + my $old_holds = $editor->search_action_hold_request( + { current_copy => $copy->id, + cancel_time => undef, + capture_time => undef + } + ); - # We know this hold is permitted, so just return it - return $hold if $hold; + # hold->type "R" means we need this copy + for my $h (@$old_holds) { return ($h) if $h->hold_type eq 'R'; } - $logger->debug("searching for potential holds at org ". - $user->ws_ou." and copy ".$copy->id); + $logger->info("circulator: searching for best hold at org ".$user->ws_ou." and copy $bc"); - my $holds = $session->request( + # search for what should be the best holds for this copy to fulfill + my $best_holds = $U->storagereq( "open-ils.storage.action.hold_request.nearest_hold.atomic", - $user->ws_ou, $copy->id, 5 )->gather(1); + $user->ws_ou, $copy->id, 10 ); + + unless(@$best_holds) { + + if( my $hold = $$old_holds[0] ) { + $logger->info("circulator: using existing pre-targeted hold ".$hold->id." in hold search"); + return ($hold); + } + + $logger->info("circulator: no suitable holds found for copy $bc"); + return (undef, $evt); + } + - return (undef, $evt) unless @$holds; + my $best_hold; # for each potential hold, we have to run the permit script # to make sure the hold is actually permitted. - - for my $holdid (@$holds) { + for my $holdid (@$best_holds) { next unless $holdid; - $logger->info("Checking if hold $holdid is permitted for user ".$user->id); - - my ($hold) = $U->fetch_hold($holdid); - next unless $hold; - my ($reqr) = $U->fetch_user($hold->requestor); + $logger->info("circulator: checking if hold $holdid is permitted for copy $bc"); - my ($rlib) = $U->fetch_org_unit($hold->request_lib); + my $hold = $editor->retrieve_action_hold_request($holdid) or next; + my $reqr = $editor->retrieve_actor_user($hold->requestor) or next; + my $rlib = $editor->retrieve_actor_org_unit($hold->request_lib) or next; - return ($hold) if OpenILS::Utils::PermitHold::permit_copy_hold( - { - patron_id => $hold->usr, + # see if this hold is permitted + my $permitted = OpenILS::Utils::PermitHold::permit_copy_hold( + { patron_id => $hold->usr, requestor => $reqr->id, copy => $copy, pickup_lib => $hold->pickup_lib, request_lib => $rlib, } ); + + if( $permitted ) { + $best_hold = $hold; + last; + } + } + + + unless( $best_hold ) { # no "good" permitted holds were found + if( my $hold = $$old_holds[0] ) { # can we return a pre-targeted hold? + $logger->info("circulator: using existing pre-targeted hold ".$hold->id." in hold search"); + return ($hold); + } + + # we got nuthin + $logger->info("circulator: no suitable holds found for copy $bc"); + return (undef, $evt); + } + + $logger->info("circulator: best hold ".$best_hold->id." found for copy $bc"); + + # indicate a permitted hold was found + return $best_hold if $check_only; + + # we've found a permitted hold. we need to "grab" the copy + # to prevent re-targeted holds (next part) from re-grabbing the copy + $best_hold->current_copy($copy->id); + $editor->update_action_hold_request($best_hold) + or return (undef, $editor->event); + + + # re-target any other holds that already target this copy + for my $old_hold (@$old_holds) { + next if $old_hold->id eq $best_hold->id; # don't re-target the hold we want + $logger->info("circulator: re-targeting hold ".$old_hold->id. + " after a better hold [".$best_hold->id."] was found"); + $U->storagereq( + 'open-ils.storage.action.hold_request.copy_targeter', undef, $old_hold->id ); } - return (undef, $evt); + return ($best_hold); } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm index a7f728ffa8..4956fe0f79 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/Money.pm @@ -76,12 +76,16 @@ sub make_payments { my $approval_code = $payments->{approval_code} || 'n/a'; my $check_number = $payments->{check_number} || 'n/a'; + my $total_paid = 0; + for my $pay (@{$payments->{payments}}) { my $transid = $pay->[0]; my $amount = $pay->[1]; $amount =~ s/\$//og; # just to be safe + $total_paid += $amount; + $trans = $self->fetch_mbts($client, $login, $transid); return $trans if $U->event_code($trans); @@ -129,10 +133,10 @@ sub make_payments { $payobj->amount($amount); $payobj->amount_collected($amount); - $payobj->accepting_usr($user->id); $payobj->xact($transid); $payobj->note($note); + if ($payobj->has_field('accepting_usr')) { $payobj->accepting_usr($user->id); } if ($payobj->has_field('cash_drawer')) { $payobj->cash_drawer($drawer); } if ($payobj->has_field('cc_type')) { $payobj->cc_type($cc_type); } if ($payobj->has_field('cc_number')) { $payobj->cc_number($cc_number); } @@ -177,9 +181,12 @@ sub make_payments { } - $logger->activity("user ".$user->id." applying total ". + my $uid = $user->id; + $logger->info("user $uid applying total ". "credit of $credit to user $userid") if $credit != 0; + $logger->info("user $uid applying total payment of $total_paid to user $userid"); + $evt = _update_patron_credit( $session, $userid, $credit ); return $evt if $evt; @@ -410,54 +417,54 @@ __PACKAGE__->register_method( / ); + sub void_bill { - my( $s, $c, $authtoken, $billid ) = @_; + my( $s, $c, $authtoken, @billids ) = @_; - #my $e = OpenILS::Utils::Editor->new( authtoken => $authtoken ); my $e = new_editor( authtoken => $authtoken, xact => 1 ); return $e->event unless $e->checkauth; return $e->event unless $e->allowed('VOID_BILLING'); - my $bill = $e->retrieve_money_billing($billid) - or return $e->event; - - return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill) - if $bill->voided and $bill->voided =~ /t/io; - - $bill->voided('t'); - $bill->voider($e->requestor->id); - $bill->void_time('now'); - - $e->update_money_billing($bill) or return $e->event; - my $evt = _check_open_xact($e, $bill->xact); - return $evt if $evt; + my %users; + for my $billid (@billids) { + + my $bill = $e->retrieve_money_billing($billid) + or return $e->event; + + my $xact = $e->retrieve_money_billable_transaction($bill->xact) + or return $e->event; + + $users{$xact->usr} = 1; + + return OpenILS::Event->new('BILL_ALREADY_VOIDED', payload => $bill) + if $bill->voided and $bill->voided =~ /t/io; + + $bill->voided('t'); + $bill->voider($e->requestor->id); + $bill->void_time('now'); + + $e->update_money_billing($bill) or return $e->event; + my $evt = _check_open_xact($e, $bill->xact, $xact); + return $evt if $evt; + } $e->commit; - - # ------------------------------------------------------------------------------ - # Update the patron penalty info in the DB - # ------------------------------------------------------------------------------ - my $xact = $e->retrieve_money_billable_transaction($bill->xact) - or return $e->event; - - $U->update_patron_penalties( - authtoken => $authtoken, - patronid => $xact->usr, - ); - + # update the penalties for each affected user + $U->update_patron_penalties( authtoken => $authtoken, patronid => $_ ) for keys %users; return 1; } + sub _check_open_xact { - my( $editor, $xactid ) = @_; + my( $editor, $xactid, $xact ) = @_; # Grab the transaction - my $xact = $editor->retrieve_money_billable_transaction($xactid) - or return $editor->event; + $xact ||= $editor->retrieve_money_billable_transaction($xactid); + return $editor->event unless $xact; + $xactid ||= $xact->id; # grab the summary and see how much is owed on this transaction - my $summary = $editor->retrieve_money_billable_transaction_summary($xactid) - or return $editor->event; + my ($summary) = $U->fetch_mbts($xactid, $editor); # grab the circulation if it is a circ; my $circ = $editor->retrieve_action_circulation($xactid); diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Circ/ScriptBuilder.pm b/Open-ILS/src/perlmods/OpenILS/Application/Circ/ScriptBuilder.pm index 2f007146a3..41ab511ee0 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Circ/ScriptBuilder.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Circ/ScriptBuilder.pm @@ -104,6 +104,7 @@ sub build_runner { insert_org_methods( $editor, $runner ); insert_copy_methods( $editor, $ctx, $runner ); + insert_user_funcs( $editor, $ctx, $runner ); return $runner; } @@ -369,14 +370,27 @@ sub insert_copy_methods { $runner->insert_method( 'environment.copy', '__OILS_FUNC_fetch_best_hold', sub { my $key = shift; $logger->debug("script_builder: searching for permitted hold for copy ".$copy->barcode); - my ($hold) = $holdcode->find_nearest_permitted_hold( - OpenSRF::AppSession->create('open-ils.storage'), $copy, $reqr ); + my ($hold) = $holdcode->find_nearest_permitted_hold( $e, $copy, $reqr, 1 ); # do we need a new editor here since the xact may be dead?? $runner->insert( $key, $hold, 1 ); } ); } } +sub insert_user_funcs { + my( $e, $ctx, $runner ) = @_; + + # tells how many holds a user has + $runner->insert(__OILS_FUNC_userHoldCount => + sub { + my( $write_key, $userid ) = @_; + my $val = $holdcode->__user_hold_count(new_editor(), $userid); + $logger->info("script_runner: user hold count is $val"); + $runner->insert($write_key, $val, 1) if $val; + return $val; + } + ); +} diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm b/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm index 0b03285b14..0c53073a0b 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Collections.pm @@ -40,6 +40,7 @@ __PACKAGE__->register_method( api_name => 'open-ils.collections.users_of_interest.retrieve', api_level => 1, argc => 4, + stream => 1, signature => { desc => q/ Returns an array of user information objects that the system @@ -107,34 +108,53 @@ sub users_of_interest { # they need global perms to view users so no org is provided return $e->event unless $e->allowed('VIEW_USER'); - my $data = $U->storagereq( - 'open-ils.storage.money.collections.users_of_interest.atomic', - $age, $fine_level, $location); - - return [] unless $data and @$data; - - for (@$data) { - my $u = $e->retrieve_actor_user( - [ - $_->{usr}, - { - flesh => 1, - flesh_fields => {au => ["groups","profile", "card"]}, - select => {au => ["profile","id","dob", "card"]} - } - ] - ) or return $e->event; - - $_->{usr} = { - id => $u->id, - dob => $u->dob, - profile => $u->profile->name, - barcode => $u->card->barcode, - groups => [ map { $_->name } @{$u->groups} ], - }; - } - - return $data; + my $data = []; + + my $ses = OpenSRF::AppSession->create('open-ils.storage'); + + my $start = time; + my $req = $ses->request( + 'open-ils.storage.money.collections.users_of_interest', + $age, $fine_level, $location); + + # let the client know we're still here + $conn->status( new OpenSRF::DomainObject::oilsContinueStatus ); + + my $total; + while( my $resp = $req->recv(timeout => 600) ) { + return $req->failed if $req->failed; + my $hash = $resp->content; + next unless $hash; + + unless($total) { + $total = time - $start; + $logger->info("collections: users_of_interest ". + "($age, $fine_level, $location) took $total seconds"); + } + + my $u = $e->retrieve_actor_user( + [ + $hash->{usr}, + { + flesh => 1, + flesh_fields => {au => ["groups","profile", "card"]}, + #select => {au => ["profile","id","dob", "card"]} + } + ] + ) or return $e->event; + + $hash->{usr} = { + id => $u->id, + dob => $u->dob, + profile => $u->profile->name, + barcode => $u->card->barcode, + groups => [ map { $_->name } @{$u->groups} ], + }; + + $conn->respond($hash); + } + + return undef; } @@ -143,6 +163,7 @@ __PACKAGE__->register_method( api_name => 'open-ils.collections.users_with_activity.retrieve', api_level => 1, argc => 4, + stream => 1, signature => { desc => q/ Returns an array of users that are already in collections @@ -189,11 +210,31 @@ sub users_with_activity { my $org = $e->search_actor_org_unit({shortname => $location}) or return $e->event; $org = $org->[0]; - return $e->event unless $e->allowed('VIEW_USER', $org->id); + return $e->event unless $e->allowed('VIEW_USER', $org->id); + + my $ses = OpenSRF::AppSession->create('open-ils.storage'); - return $U->storagereq( + my $start = time; + my $req = $ses->request( 'open-ils.storage.money.collections.users_with_activity.atomic', $start_date, $end_date, $location); + + $conn->status( new OpenSRF::DomainObject::oilsContinueStatus ); + + my $total; + while( my $resp = $req->recv(timeout => 600) ) { + + unless($total) { + $total = time - $start; + $logger->info("collections: users_with_activity search ". + "($start_date, $end_date, $location) took $total seconds"); + } + + return $req->failed if $req->failed; + $conn->respond($resp->content); + } + + return undef; } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Reporter.pm b/Open-ILS/src/perlmods/OpenILS/Application/Reporter.pm index 127dbb30cb..43eb35eb15 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Reporter.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Reporter.pm @@ -154,6 +154,9 @@ sub create_template { } + + + __PACKAGE__->register_method( api_name => 'open-ils.reporter.report.create', method => 'create_report'); @@ -335,6 +338,146 @@ sub delete_template { return 1; } + + +__PACKAGE__->register_method( + api_name => 'open-ils.reporter.template.delete.cascade', + method => 'cascade_delete_template'); + +#__PACKAGE__->register_method( +# api_name => 'open-ils.reporter.template.delete.cascade.force', +# method => 'cascade_delete_template'); + +sub cascade_delete_template { + my( $self, $conn, $auth, $templateId ) = @_; + + my $e = new_rstore_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + return $e->die_event unless $e->allowed('RUN_REPORTS'); + + my $ret = cascade_delete_template_impl( + $e, $e->requestor->id, $templateId, ($self->api_name =~ /force/o) ); + return $ret if ref $ret; # some fatal event occurred + + $e->rollback if $ret == 0; + $e->commit if $ret > 0; + return $ret; +} + + +__PACKAGE__->register_method( + api_name => 'open-ils.reporter.report.delete.cascade', + method => 'cascade_delete_report'); + +#__PACKAGE__->register_method( +# api_name => 'open-ils.reporter.report.delete.cascade.force', +# method => 'cascade_delete_report'); + +sub cascade_delete_report { + my( $self, $conn, $auth, $reportId ) = @_; + + my $e = new_rstore_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + return $e->die_event unless $e->allowed('RUN_REPORTS'); + + my $ret = cascade_delete_report_impl($e, $e->requestor->id, $reportId); + return $ret if ref $ret; # some fatal event occurred + + $e->rollback if $ret == 0; + $e->commit if $ret > 0; + return $ret; +} + + +# performs a cascading template delete +# returns 2 if all data was deleted +# returns 1 if some data was deleted +# returns 0 if no data was deleted +# returns event on error +sub cascade_delete_template_impl { + my( $e, $owner, $templateId ) = @_; + + # fetch the template to delete + my $template = $e->search_reporter_template( + {id=>$templateId, owner=>$owner})->[0] or return 0; + + # fetch he attached report IDs for this owner + my $reports = $e->search_reporter_report( + {template=>$templateId, owner=>$owner},{idlist=>1}); + + # delete the attached reports + my $all_rpts_deleted = 1; + for my $r (@$reports) { + my $evt = cascade_delete_report_impl($e, $owner, $r); + return $evt if ref $evt; + $all_rpts_deleted = 0 unless $evt == 2; + } + + # fetch all reports attached to this template that + # do not belong to $owner. If there are any, we can't + # delete the template + my $alt_reports = $e->search_reporter_report( + {template=>$templateId, owner=>{"!=" => $owner}},{idlist=>1}); + + # all_rpts_deleted will be false if a report has an + # attached scheduled owned by a different user + return 1 if @$alt_reports or not $all_rpts_deleted; + + $e->delete_reporter_template($template) + or return $e->die_event; + return 2; +} + +# performs a cascading report delete +# returns 2 if all data was deleted +# returns 1 if some data was deleted +# returns 0 if no data was deleted +# returns event on error +sub cascade_delete_report_impl { + my( $e, $owner, $reportId ) = @_; + + # fetch the report to delete + my $report = $e->search_reporter_report( + {id=>$reportId, owner=>$owner})->[0] or return 0; + + # fetch the attached schedule IDs for this owner + my $scheds = $e->search_reporter_schedule( + {report=>$reportId, runner=>$owner},{idlist=>1}); + + # delete the attached schedules + for my $sched (@$scheds) { + my $evt = delete_schedule_impl($e, $sched); + return $evt if $evt; + } + + # fetch all schedules attached to this report that + # do not belong to $owner. If there are any, we can't + # delete the report + my $alt_scheds = $e->search_reporter_schedule( + {report=>$reportId, runner=>{"!=" => $owner}},{idlist=>1}); + + return 1 if @$alt_scheds; + + $e->delete_reporter_report($report) + or return $e->die_event; + + return 2; +} + + +# deletes the requested schedule +# returns undef on success, event on error +sub delete_schedule_impl { + my( $e, $schedId ) = @_; + my $s = $e->retrieve_reporter_schedule($schedId) + or return $e->die_event; + $e->delete_reporter_schedule($s) or return $e->die_event; + return undef; +} + + + + __PACKAGE__->register_method( api_name => 'open-ils.reporter.report.delete', method => 'delete_report'); diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Search/Biblio.pm b/Open-ILS/src/perlmods/OpenILS/Application/Search/Biblio.pm index 0d7e20ca7e..1dcf503f60 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Search/Biblio.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Search/Biblio.pm @@ -175,13 +175,14 @@ __PACKAGE__->register_method( sub biblio_search_tcn { - my( $self, $client, $tcn ) = @_; + my( $self, $client, $tcn, $include_deleted ) = @_; $tcn =~ s/.*?(\w+)\s*$/$1/o; my $e = new_editor(); - my $recs = $e->search_biblio_record_entry( - {deleted => 'f', tcn_value => $tcn}, {idlist =>1}); + my $search = {tcn_value => $tcn}; + $search->{deleted} = 'f' unless $include_deleted; + my $recs = $e->search_biblio_record_entry( $search, {idlist =>1} ); return { count => scalar(@$recs), ids => $recs }; } @@ -356,6 +357,31 @@ sub biblio_barcode_to_title { return { count => 0 }; } +__PACKAGE__->register_method( + method => 'title_id_by_item_barcode', + api_name => 'open-ils.search.bib_id.by_barcode' +); + +sub title_id_by_item_barcode { + my( $self, $conn, $barcode ) = @_; + my $e = new_editor(); + my $copies = $e->search_asset_copy( + [ + { deleted => 'f', barcode => $barcode }, + { + flesh => 2, + flesh_fields => { + acp => [ 'call_number' ], + acn => [ 'record' ] + } + } + ] + ); + + return $e->event unless @$copies; + return $$copies[0]->call_number->record->id; +} + __PACKAGE__->register_method( method => "biblio_copy_to_mods", @@ -1019,9 +1045,13 @@ sub marc_search { my $recs = search_cache($ckey, $offset, $limit); if(!$recs) { - $recs = $U->storagereq($method, %$args); - put_cache($ckey, scalar(@$recs), $recs); - $recs = [ @$recs[$offset..($offset + ($limit - 1))] ]; + $recs = $U->storagereq($method, %$args) || []; + if( $recs ) { + put_cache($ckey, scalar(@$recs), $recs); + $recs = [ @$recs[$offset..($offset + ($limit - 1))] ]; + } else { + $recs = []; + } } my $count = 0; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Search/Z3950.pm b/Open-ILS/src/perlmods/OpenILS/Application/Search/Z3950.pm index c8efb75a7e..06c99525e4 100755 --- a/Open-ILS/src/perlmods/OpenILS/Application/Search/Z3950.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Search/Z3950.pm @@ -191,7 +191,7 @@ sub do_search { 'Z3950_BAD_QUERY', payload => $query, debug => "$err") if $err; return OpenILS::Event->new('Z3950_SEARCH_FAILED', - debug => $connection->errcode.":".$connection->errmsg) unless $results; + debug => $connection->errcode." => ".$connection->errmsg." : query = $query") unless $results; $logger->info("z3950: search [$query] took ".(time - $start)." seconds"); @@ -277,10 +277,18 @@ sub compile_query { my $str = ""; $str .= "\@$seperator " for (1..$count-1); + # ------------------------------------------------------------------- + # "code" is the bib-1 "use attribute", "format" is the bib-1 + # "structure attribute" + # ------------------------------------------------------------------- for( keys %$hash ) { - $str .= '@attr ' . - $services{$service}->{attrs}->{$_}->{format} . '=' . - $services{$service}->{attrs}->{$_}->{code} . " \"" . $$hash{$_} . "\" "; +# $str .= '@attr ' . +# $services{$service}->{attrs}->{$_}->{format} . '=' . +# $services{$service}->{attrs}->{$_}->{code} . " \"" . $$hash{$_} . "\" "; + $str .= + '@attr 1=' . $services{$service}->{attrs}->{$_}->{code} . # add the use attribute + ' @attr 4=' . $services{$service}->{attrs}->{$_}->{format} . # add teh structure attribute + " \"" . $$hash{$_} . "\" "; # add the search term } return $str; } @@ -301,6 +309,10 @@ sub entityize { } $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe; + + # strip some other unfriendly chars that may leak in + $stuff =~ s/([\x{0000}-\x{0008}])//sgoe; + return $stuff; } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/actor.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/actor.pm index ee4fb5adea..c2e631b900 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/actor.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/CDBI/actor.pm @@ -74,7 +74,7 @@ use base qw/actor/; __PACKAGE__->table( 'actor_org_unit' ); __PACKAGE__->columns( Primary => qw/id/); __PACKAGE__->columns( Essential => qw/parent_ou ou_type mailing_address billing_address - ill_address holds_address shortname name email phone/); + ill_address holds_address shortname name email phone opac_visible/); #------------------------------------------------------------------------------- package actor::org_unit::hours_of_operation; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg.pm index 0b3f84cc06..ca53690ee7 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg.pm @@ -29,6 +29,11 @@ my $master_db; my @slave_dbs; my $_db_params; + + sub db_Handles { + return ($master_db, @slave_dbs); + } + sub child_init { my $self = shift; $_db_params = shift; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/storage.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/storage.pm index ada1ce4858..c71867f8b4 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/storage.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Driver/Pg/storage.pm @@ -2,11 +2,14 @@ { package OpenILS::Application::Storage; use OpenSRF::Utils::Logger; + our $NOPRIMARY = 0; my $log = 'OpenSRF::Utils::Logger'; - my $pg = 'OpenILS::Application::Storage::Driver::Pg'; + sub child_exit { + $_->disconnect for $pg->db_Handles; + } sub current_xact { my $self = shift; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm index a6f615ec88..e8ae0f5e66 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/action.pm @@ -184,7 +184,7 @@ sub nearest_hold { my $cp = shift; my $limit = int(shift()) || 10; - my ($id) = action::hold_request->db_Main->selectrow_array(<<" SQL", {}, $cp, $pl); + my $ids = action::hold_request->db_Main->selectcol_arrayref(<<" SQL", {}, $cp, $pl); SELECT h.id FROM action.hold_request h JOIN action.hold_copy_map hm ON (hm.hold = h.id) @@ -199,7 +199,9 @@ sub nearest_hold { h.request_time LIMIT $limit SQL - return $id; + + $client->respond( $_ ) for ( @$ids ); + return undef; } __PACKAGE__->register_method( api_name => 'open-ils.storage.action.hold_request.nearest_hold', @@ -687,6 +689,7 @@ sub new_hold_copy_targeter { try { if ($one_hold) { + $self->method_lookup('open-ils.storage.transaction.begin')->run( $client ); $holds = [ action::hold_request->search_where( { id => $one_hold, fulfillment_time => undef, cancel_time => undef } ) ]; } elsif ( $check_expire ) { @@ -735,8 +738,8 @@ sub new_hold_copy_targeter { }; my @closed = actor::org_unit::closed_date->search_where( - { close_start => { '<=', 'today' }, - close_end => { '>=', 'today' } } + { close_start => { '<=', 'now' }, + close_end => { '>=', 'now' } } ); @@ -744,10 +747,6 @@ sub new_hold_copy_targeter { for my $hold (@$holds) { try { - #first, re-fetch the hold, to make sure it's not captured already - $hold = action::hold_request->retrieve( $hold->id ); - die "OK\n" if (!$hold or $hold->capture_time); - #start a transaction if needed if ($self->method_lookup('open-ils.storage.transaction.current')->run) { $log->debug("Cleaning up after previous transaction\n"); @@ -756,11 +755,14 @@ sub new_hold_copy_targeter { $self->method_lookup('open-ils.storage.transaction.begin')->run( $client ); $log->info("Processing hold ".$hold->id."...\n"); + #first, re-fetch the hold, to make sure it's not captured already + $hold = action::hold_request->retrieve( $hold->id ); + die "OK\n" if (!$hold or $hold->capture_time); + # remove old auto-targeting maps my @oldmaps = action::hold_copy_map->search( hold => $hold->id ); $_->delete for (@oldmaps); - my $all_copies = []; # find filters for MR holds @@ -784,7 +786,10 @@ sub new_hold_copy_targeter { for my $cn ( @{ $rtree->call_numbers } ) { push @$all_copies, - asset::copy->search( id => [map {$_->id} @{ $cn->copies }] ); + asset::copy->search_where( + { id => [map {$_->id} @{ $cn->copies }], + deleted => 'f' } + ) if ($cn && @{ $cn->copies }); } } } elsif ($hold->hold_type eq 'T') { @@ -799,7 +804,10 @@ sub new_hold_copy_targeter { for my $cn ( @{ $rtree->call_numbers } ) { push @$all_copies, - asset::copy->search( id => [map {$_->id} @{ $cn->copies }] ); + asset::copy->search_where( + { id => [map {$_->id} @{ $cn->copies }], + deleted => 'f' } + ) if ($cn && @{ $cn->copies }); } } elsif ($hold->hold_type eq 'V') { my ($vtree) = $self @@ -807,17 +815,21 @@ sub new_hold_copy_targeter { ->run( $hold->target, $hold->selection_ou, $hold->selection_depth ); push @$all_copies, - asset::copy->search( id => [map {$_->id} @{ $vtree->copies }] ); + asset::copy->search_where( + { id => [map {$_->id} @{ $vtree->copies }], + deleted => 'f' } + ) if ($vtree && @{ $vtree->copies }); - } elsif ($hold->hold_type eq 'C') { - - $all_copies = [asset::copy->retrieve($hold->target)]; + } elsif ($hold->hold_type eq 'C' || $hold->hold_type eq 'R' || $hold->hold_type eq 'F') { + my $_cp = asset::copy->retrieve($hold->target); + push @$all_copies, $_cp if $_cp; } # trim unholdables @$all_copies = grep { isTrue($_->status->holdable) && isTrue($_->location->holdable) && - isTrue($_->holdable) + isTrue($_->holdable) && + !isTrue($_->deleted) } @$all_copies; # let 'em know we're still working @@ -828,7 +840,7 @@ sub new_hold_copy_targeter { $log->info("\tNo copies available for targeting at all!\n"); push @successes, { hold => $hold->id, eligible_copies => 0, error => 'NO_COPIES' }; - $hold->update( { prev_check_time => 'today' } ); + $hold->update( { prev_check_time => 'today', current_copy => undef } ); $self->method_lookup('open-ils.storage.transaction.commit')->run; die "OK\n"; } @@ -844,10 +856,10 @@ sub new_hold_copy_targeter { my @good_copies; for my $c (@$all_copies) { # current target - next if ($c->id == $hold->current_copy); + next if ($c->id eq $hold->current_copy); # circ lib is closed - next if ( grep { ''.$_->org_unit == ''.$c->circ_lib } @closed ); + next if ( grep { ''.$_->org_unit eq ''.$c->circ_lib } @closed ); # target of another hold next if (action::hold_request @@ -928,6 +940,18 @@ sub new_hold_copy_targeter { if ($best) { $hold->update( { current_copy => ''.$best->id, prev_check_time => 'now' } ); $log->debug("\tUpdating hold [".$hold->id."] with new 'current_copy' [".$best->id."] for hold fulfillment."); + } elsif ( + $old_best && + action::hold_request + ->search_where( + { current_copy => $old_best->id, + fulfillment_time => undef, + cancel_time => undef, + } + ) + ) { + $hold->update( { prev_check_time => 'now', current_copy => ''.$old_best->id } ); + $log->debug( "\tRetargeting the previously targeted copy [".$old_best->id."]" ); } else { $hold->update( { prev_check_time => 'now' } ); $log->info( "\tThere were no targetable copies for the hold" ); @@ -1193,20 +1217,19 @@ sub choose_nearest_copy { my $rand = int(rand(scalar(@capturable))); while (my ($c) = splice(@capturable,$rand)) { - unless ( OpenILS::Utils::PermitHold::permit_copy_hold( + return $c if ( OpenILS::Utils::PermitHold::permit_copy_hold( { title => $c->call_number->record->to_fieldmapper, title_descriptor => $c->call_number->record->record_descriptor->next->to_fieldmapper, patron => $hold->usr->to_fieldmapper, copy => $c->to_fieldmapper, requestor => $hold->requestor->to_fieldmapper, request_lib => $hold->request_lib->to_fieldmapper, + pickup_lib => $hold->pickup_lib->id, } - )) { - last unless(@capturable); - $rand = int(rand(scalar(@capturable))); - next; - } - return $c; + )); + + last unless(@capturable); + $rand = int(rand(scalar(@capturable))); } } } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/actor.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/actor.pm index f6f3f72424..406c06ba3a 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/actor.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/actor.pm @@ -70,7 +70,7 @@ sub usr_breakdown_out { my $lost_sql = <<" SQL"; SELECT id FROM action.circulation - WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'LOST' + WHERE usr = ? AND checkin_time IS NULL AND xact_finish IS NULL AND stop_fines = 'LOST' SQL my $lost = actor::user->db_Main->selectcol_arrayref($lost_sql, {}, $usr); @@ -188,16 +188,16 @@ sub org_closed_overlap { WHERE ? between close_start and close_end AND org_unit = ? ORDER BY close_start ASC, close_end DESC + LIMIT 1 SQL $date = clense_ISO8601($date); + my ($begin, $end) = ($date,$date); - my $sth = actor::org_unit::closed_date->db_Main->prepare( $sql ); - $sth->execute($date, $ou); - - my ($begin, $end); - while (my $closure = $sth->fetchrow_hashref) { - $begin ||= clense_ISO8601($closure->{close_start}); + my $hoo = actor::org_unit::hours_of_operation->retrieve($ou); + + if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $date, $ou )) { + $begin = clense_ISO8601($closure->{close_start}); $end = clense_ISO8601($closure->{close_end}); if ( $direction <= 0 ) { @@ -219,45 +219,63 @@ sub org_closed_overlap { } $end = clense_ISO8601($after->iso8601); } - } - $begin ||= $date; - $end ||= $date; - - if ( !$no_hoo ) { - if ( my $hoo = actor::org_unit::hours_of_operation->retrieve($ou) ) { - - my $begin_dow = $_dt_parser->parse_datetime( $begin )->day_of_week_0; - my $begin_open_meth = "dow_".$begin_dow."_open"; - my $begin_close_meth = "dow_".$begin_dow."_close"; - - my $count = 1; - while ($hoo->$begin_open_meth eq '00:00:00' and $hoo->$begin_close_meth eq '00:00:00') { - $begin = clense_ISO8601($_dt_parser->parse_datetime( $begin )->subtract( days => 1)->iso8601); - $begin_dow++; - $begin_dow %= 7; - $count++; - last if ($count > 6); - $begin_open_meth = "dow_".$begin_dow."_open"; - $begin_close_meth = "dow_".$begin_dow."_close"; + if ( $hoo ) { + + if ( $direction <= 0 ) { + my $begin_dow = $_dt_parser->parse_datetime( $begin )->day_of_week_0; + my $begin_open_meth = "dow_".$begin_dow."_open"; + my $begin_close_meth = "dow_".$begin_dow."_close"; + + my $count = 1; + while ($hoo->$begin_open_meth eq '00:00:00' and $hoo->$begin_close_meth eq '00:00:00') { + $begin = clense_ISO8601($_dt_parser->parse_datetime( $begin )->subtract( days => 1)->iso8601); + $begin_dow++; + $begin_dow %= 7; + $count++; + last if ($count > 6); + $begin_open_meth = "dow_".$begin_dow."_open"; + $begin_close_meth = "dow_".$begin_dow."_close"; + } + + if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $begin, $ou )) { + $before = $_dt_parser->parse_datetime( $begin ); + $before->subtract( minutes => 1 ); + while ( my $_b = org_closed_overlap($self, $client, $ou, $before->iso8601, -1 ) ) { + $before = $_dt_parser->parse_datetime( clense_ISO8601($_b->{start}) ); + } + } } - my $end_dow = $_dt_parser->parse_datetime( $end )->day_of_week_0; - my $end_open_meth = "dow_".$end_dow."_open"; - my $end_close_meth = "dow_".$end_dow."_close"; + if ( $direction >= 0 ) { + my $end_dow = $_dt_parser->parse_datetime( $end )->day_of_week_0; + my $end_open_meth = "dow_".$end_dow."_open"; + my $end_close_meth = "dow_".$end_dow."_close"; - $count = 1; - while ($hoo->$end_open_meth eq '00:00:00' and $hoo->$end_close_meth eq '00:00:00') { - $end = clense_ISO8601($_dt_parser->parse_datetime( $end )->add( days => 1)->iso8601); - $end_dow++; - $end_dow %= 7; - $count++; - last if ($count > 6); - $end_open_meth = "dow_".$end_dow."_open"; - $end_close_meth = "dow_".$end_dow."_close"; + $count = 1; + while ($hoo->$end_open_meth eq '00:00:00' and $hoo->$end_close_meth eq '00:00:00') { + $end = clense_ISO8601($_dt_parser->parse_datetime( $end )->add( days => 1)->iso8601); + $end_dow++; + $end_dow %= 7; + $count++; + last if ($count > 6); + $end_open_meth = "dow_".$end_dow."_open"; + $end_close_meth = "dow_".$end_dow."_close"; + } + + if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $end, $ou )) { + $after = $_dt_parser->parse_datetime( $end ); + $after->add( minutes => 1 ); + + while ( my $_a = org_closed_overlap($self, $client, $ou, $after->iso8601, 1 ) ) { + $after = $_dt_parser->parse_datetime( clense_ISO8601($_a->{end}) ); + } + $end = clense_ISO8601($after->iso8601); + } } + } } @@ -420,7 +438,9 @@ sub patron_search { my $limit = shift || 1000; my $sort = shift; my $inactive = shift; + $sort = ['family_name','first_given_name'] unless ($$sort[0]); + push @$sort,'id'; # group 0 = user # group 1 = address @@ -496,7 +516,8 @@ sub patron_search { return undef; } - my $order_by = join ', ', map { 'users.'. $_} @$sort; + my $order_by = join ', ', map { 'LOWER(users.'. (split / /,$_)[0] . ') ' . (split / /,$_)[1] } @$sort; + my $distinct_list = join ', ', map { 'LOWER(users.'. (split / /,$_)[0] . ')' } @$sort; if ($inactive) { $inactive = ''; @@ -505,17 +526,16 @@ sub patron_search { } $select = <<" SQL"; - SELECT users.id + SELECT DISTINCT $distinct_list FROM $u_table AS users - JOIN ($select) AS search - USING (id) - $clone_select + JOIN ($select) AS search USING (id) + $clone_select WHERE users.deleted = FALSE $inactive ORDER BY $order_by LIMIT $limit SQL - return actor::user->db_Main->selectcol_arrayref($select, {}, map {lc($_)} (@usrv,@phonev,@identv,@namev,@addrv,@addrv)); + return actor::user->db_Main->selectcol_arrayref($select, {Columns=>[scalar(@$sort)]}, map {lc($_)} (@usrv,@phonev,@identv,@namev,@addrv,@addrv)); } __PACKAGE__->register_method( api_name => 'open-ils.storage.actor.user.crazy_search', diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/asset.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/asset.pm index 839ca8fb0f..80576d729b 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/asset.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/asset.pm @@ -766,6 +766,7 @@ sub cn_ranged_tree { $cn = asset::call_number->retrieve( $cn ); return undef unless ($cn); + return undef if ($cn->deleted); my $call_number = $cn->to_fieldmapper; $call_number->copies([]); @@ -774,6 +775,7 @@ sub cn_ranged_tree { $call_number->record->fixed_fields( $cn->record->record_descriptor->next->to_fieldmapper ); for my $cp ( $cn->copies(circ_lib => $ou_list) ) { + next if ($cp->deleted); my $copy = $cp->to_fieldmapper; $copy->status( $cp->status->to_fieldmapper ); $copy->location( $cp->status->to_fieldmapper ); diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/biblio.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/biblio.pm index 2f7b5ca916..b73dcad689 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/biblio.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/biblio.pm @@ -25,7 +25,7 @@ sub record_copy_count { my $descendants = "actor.org_unit_descendants(u.id)"; my $ancestors = "actor.org_unit_ancestors(?)"; - my $visible = 'AND st.holdable = TRUE AND loc.opac_visible = TRUE AND cp.opac_visible = TRUE'; + my $visible = 'AND a.opac_visible = TRUE AND st.holdable = TRUE AND loc.opac_visible = TRUE AND cp.opac_visible = TRUE'; if ($self->api_name =~ /staff/o) { $visible = '' } @@ -142,11 +142,13 @@ sub record_ranged_tree { my $offset_count = 0; my $limit_count = 0; for my $cn ( $r->call_numbers ) { + next if ($cn->deleted); my $call_number = $cn->to_fieldmapper; $call_number->copies([]); for my $cp ( $cn->copies(circ_lib => $ou_list) ) { + next if ($cp->deleted); if ($offset > 0 && $offset_count < $offset) { $offset_count++; next; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/metabib.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/metabib.pm index 33c7f3c50e..aae5faaf1d 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/metabib.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/metabib.pm @@ -36,7 +36,7 @@ sub ordered_records_from_metarecord { "actor.org_unit_descendants($org)" ; - my $copies_visible = 'AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; + my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; $copies_visible = '' if ($self->api_name =~ /staff/o); my $sm_table = metabib::metarecord_source_map->table; @@ -218,18 +218,21 @@ sub isxn_search { $isxn =~ s/\s*$//o; $isxn =~ s/-//o if ($self->api_name =~ /isbn/o); - my $tag = ($self->api_name =~ /isbn/o) ? "'020' OR tag = '024'" : "'022'"; + my $tag = ($self->api_name =~ /isbn/o) ? "'020' OR f.tag = '024'" : "'022'"; my $fr_table = metabib::full_rec->table; + my $bib_table = biblio::record_entry->table; my $sql = <<" SQL"; - SELECT record - FROM $fr_table - WHERE (tag = $tag) - AND value LIKE ? + SELECT DISTINCT f.record + FROM $fr_table f + JOIN $bib_table b ON (b.id = f.record) + WHERE (f.tag = $tag) + AND f.value LIKE ? + AND b.deleted IS FALSE SQL - my $list = metabib::metarecord_source_map->db_Main->selectcol_arrayref($sql, {}, "$isxn%"); + my $list = metabib::full_rec->db_Main->selectcol_arrayref($sql, {}, "$isxn%"); $client->respond($_) for (@$list); return undef; } @@ -264,7 +267,7 @@ sub metarecord_copy_count { my $descendants = "actor.org_unit_descendants(u.id)"; my $ancestors = "actor.org_unit_ancestors(?)"; - my $copies_visible = 'AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; + my $copies_visible = 'AND a.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; $copies_visible = '' if ($self->api_name =~ /staff/o); my (@types,@forms); @@ -450,7 +453,7 @@ sub biblio_multi_search_full_rec { my $has_vols = 'AND cn.owning_lib = d.id'; my $has_copies = 'AND cp.call_number = cn.id'; - my $copies_visible = 'AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; + my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; if ($self->api_name =~ /staff/o) { $copies_visible = ''; @@ -769,7 +772,7 @@ sub search_class_fts { my $has_vols = 'AND cn.owning_lib = d.id'; my $has_copies = 'AND cp.call_number = cn.id'; - my $copies_visible = 'AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; + my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; my $visible_count = ', count(DISTINCT cp.id)'; my $visible_count_test = 'HAVING count(DISTINCT cp.id) > 0'; @@ -943,7 +946,7 @@ sub search_class_fts_count { my $has_vols = 'AND cn.owning_lib = d.id'; my $has_copies = 'AND cp.call_number = cn.id'; - my $copies_visible = 'AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; + my $copies_visible = 'AND d.opac_visible IS TRUE AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE'; if ($self->api_name =~ /staff/o) { $copies_visible = ''; $has_vols = '' if ($ou_type == 0); @@ -1240,6 +1243,7 @@ sub postfilter_search_class_fts { AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE + AND d.opac_visible IS TRUE AND br.active IS TRUE AND br.deleted IS FALSE AND ord.record = mrs.source @@ -1276,6 +1280,7 @@ sub postfilter_search_class_fts { AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE + AND d.opac_visible IS TRUE AND br.active IS TRUE AND br.deleted IS FALSE AND ord.record = mrs.source @@ -1536,6 +1541,7 @@ sub postfilter_search_multi_class_fts { my %bonus = (); $bonus{'keyword'} = [ { "CASE WHEN $search_class.value LIKE ? THEN 10 ELSE 1 END" => $SQLstring } ]; + $bonus{'author'} = [ { "CASE WHEN $search_class.value ILIKE ? THEN 10 ELSE 1 END" => $first_word } ]; $bonus{'series'} = [ { "CASE WHEN $search_class.value LIKE ? THEN 1.5 ELSE 1 END" => $first_word }, @@ -1700,6 +1706,7 @@ sub postfilter_search_multi_class_fts { AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE + AND d.opac_visible IS TRUE AND br.active IS TRUE AND br.deleted IS FALSE AND cp.deleted IS FALSE @@ -1995,7 +2002,7 @@ sub biblio_search_multi_class_fts { my %bonus = (); $bonus{'subject'} = []; - $bonus{'author'} = []; + $bonus{'author'} = [ { "CASE WHEN $search_class.value ILIKE ? THEN 1.5 ELSE 1 END" => $first_word } ]; $bonus{'keyword'} = [ { "CASE WHEN $search_class.value ILIKE ? THEN 10 ELSE 1 END" => $SQLstring } ]; @@ -2148,6 +2155,7 @@ sub biblio_search_multi_class_fts { AND cp.opac_visible IS TRUE AND cs.holdable IS TRUE AND cl.opac_visible IS TRUE + AND d.opac_visible IS TRUE AND cp.deleted IS FALSE AND cn.deleted IS FALSE LIMIT 1 diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm index ed25451394..f9b9353473 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Storage/Publisher/money.pm @@ -120,32 +120,64 @@ sub new_collections { my $descendants = "actor.org_unit_descendants((select id from actor.org_unit where shortname = ?))"; my $SQL = <<" SQL"; - SELECT lt.usr, - MAX(bl.billing_ts) AS last_pertinent_billing, - SUM(bl.amount) - COALESCE(SUM((SELECT SUM(amount) FROM money.payment WHERE xact = lt.id)),0) AS threshold_amount - FROM ( SELECT id,usr,billing_location AS location FROM money.grocery - UNION ALL - SELECT id,usr,circ_lib AS location FROM action.circulation ) AS lt - JOIN $descendants d ON (lt.location = d.id) - JOIN money.billing bl ON (lt.id = bl.xact AND bl.voided IS FALSE) - WHERE AGE(bl.billing_ts) > ? - GROUP BY lt.usr - HAVING SUM( - (SELECT COUNT(*) - FROM money.collections_tracker - WHERE usr = lt.usr - AND location in ( - (SELECT id - FROM $descendants ) - ) - ) ) = 0 - AND (SUM(bl.amount) - COALESCE(SUM((SELECT SUM(amount) FROM money.payment WHERE xact = lt.id)),0)) > ? + +select + usr, + MAX(last_billing) as last_pertinent_billing, + SUM(total_billing) - SUM(COALESCE(p.amount,0)) as threshold_amount + from (select + x.id, + x.usr, + MAX(b.billing_ts) as last_billing, + SUM(b.amount) AS total_billing + from action.circulation x + left join money.collections_tracker c ON (c.usr = x.usr AND c.location = ?) + join money.billing b on (b.xact = x.id) + where x.xact_finish is null + and c.id is null + and x.circ_lib in (XX) + and b.billing_ts < current_timestamp - ? * '1 day'::interval + and not b.voided + group by 1,2 + + union all + + select + x.id, + x.usr, + MAX(b.billing_ts) as last_billing, + SUM(b.amount) AS total_billing + from money.grocery x + left join money.collections_tracker c ON (c.usr = x.usr AND c.location = ?) + join money.billing b on (b.xact = x.id) + where x.xact_finish is null + and c.id is null + and x.billing_location in (XX) + and b.billing_ts < current_timestamp - ? * '1 day'::interval + and not b.voided + group by 1,2 + ) full_list + left join money.payment p on (full_list.id = p.xact) + group by 1 + having SUM(total_billing) - SUM(COALESCE(p.amount,0)) > ? +; SQL my @l_ids; for my $l (@loc) { - my $sth = money::collections_tracker->db_Main->prepare($SQL); - $sth->execute(uc($l), $age, uc($l), $amount ); + my ($org) = actor::org_unit->search( shortname => uc($l) ); + next unless $org; + + my $o_list = actor::org_unit->db_Main->selectcol_arrayref( "SELECT id FROM actor.org_unit_descendants(?);", {}, $org->id ); + next unless (@$o_list); + + my $o_txt = join ',' => @$o_list; + + (my $real_sql = $SQL) =~ s/XX/$o_txt/gsm; + + my $sth = money::collections_tracker->db_Main->prepare($real_sql); + $sth->execute( $org->id, $age, $org->id, $age, $amount ); + while (my $row = $sth->fetchrow_hashref) { #$row->{usr} = actor::user->retrieve($row->{usr})->to_fieldmapper; $client->respond( $row ); @@ -171,32 +203,95 @@ sub active_in_collections { my $mb = money::billing->table; my $circ = action::circulation->table; my $mg = money::grocery->table; - my $descendants = "actor.org_unit_descendants((select id from actor.org_unit where shortname = ?))"; my $SQL = <<" SQL"; - SELECT lt.usr, - MAX(bl.billing_ts) AS last_pertinent_billing, - MAX(pm.payment_ts) AS last_pertinent_payment - FROM ( SELECT id,usr,billing_location AS location, 'g'::char AS x_type FROM money.grocery - UNION ALL - SELECT id,usr,circ_lib AS location, 'c'::char AS x_type FROM action.circulation - UNION ALL - SELECT id,usr,circ_lib AS location, 'i'::char AS x_type FROM action.circulation - WHERE checkin_time between ? and ? ) AS lt - JOIN $descendants d ON (lt.location = d.id) - JOIN money.collections_tracker cl ON (lt.usr = cl.usr) - LEFT JOIN money.billing bl ON (lt.id = bl.xact) - LEFT JOIN money.payment pm ON (lt.id = pm.xact) - WHERE bl.billing_ts between ? and ? - OR pm.payment_ts between ? and ? - OR lt.x_type = 'i'::char - GROUP BY 1 +SELECT usr, + MAX(last_pertinent_billing) AS last_pertinent_billing, + MAX(last_pertinent_payment) AS last_pertinent_payment + FROM ( + SELECT lt.usr, + MAX(bl.billing_ts) AS last_pertinent_billing, + NULL::TIMESTAMPTZ AS last_pertinent_payment + FROM money.grocery lt + JOIN money.collections_tracker cl ON (lt.usr = cl.usr) + JOIN money.billing bl ON (lt.id = bl.xact) + WHERE cl.location = ? + AND lt.billing_location IN (XX) + AND bl.billing_ts BETWEEN ? AND ? + GROUP BY 1 + + UNION ALL + SELECT lt.usr, + NULL::TIMESTAMPTZ AS last_pertinent_billing, + MAX(pm.payment_ts) AS last_pertinent_payment + FROM money.grocery lt + JOIN money.collections_tracker cl ON (lt.usr = cl.usr) + JOIN money.payment pm ON (lt.id = pm.xact) + WHERE cl.location = ? + AND lt.billing_location IN (XX) + AND pm.payment_ts BETWEEN ? AND ? + GROUP BY 1 + + UNION ALL + SELECT lt.usr, + NULL::TIMESTAMPTZ AS last_pertinent_billing, + NULL::TIMESTAMPTZ AS last_pertinent_payment + FROM action.circulation lt + JOIN money.collections_tracker cl ON (lt.usr = cl.usr) + WHERE cl.location = ? + AND lt.circ_lib IN (XX) + AND lt.checkin_time BETWEEN ? AND ? + GROUP BY 1 + + UNION ALL + SELECT lt.usr, + NULL::TIMESTAMPTZ AS last_pertinent_billing, + MAX(pm.payment_ts) AS last_pertinent_payment + FROM action.circulation lt + JOIN money.collections_tracker cl ON (lt.usr = cl.usr) + JOIN money.payment pm ON (lt.id = pm.xact) + WHERE cl.location = ? + AND lt.circ_lib IN (XX) + AND pm.payment_ts BETWEEN ? AND ? + GROUP BY 1 + + UNION ALL + SELECT lt.usr, + MAX(bl.billing_ts) AS last_pertinent_billing, + NULL::TIMESTAMPTZ AS last_pertinent_payment + FROM action.circulation lt + JOIN money.collections_tracker cl ON (lt.usr = cl.usr) + JOIN money.billing bl ON (lt.id = bl.xact) + WHERE cl.location = ? + AND lt.circ_lib IN (XX) + AND bl.billing_ts BETWEEN ? AND ? + GROUP BY 1 + ) foo + GROUP BY 1 +; SQL my @l_ids; for my $l (@loc) { - my $sth = money::collections_tracker->db_Main->prepare($SQL); - $sth->execute( $startdate, $enddate, uc($l), $startdate, $enddate, $startdate, $enddate ); + my ($org) = actor::org_unit->search( shortname => uc($l) ); + next unless $org; + + my $o_list = actor::org_unit->db_Main->selectcol_arrayref( "SELECT id FROM actor.org_unit_descendants(?);", {}, $org->id ); + next unless (@$o_list); + + my $o_txt = join ',' => @$o_list; + + (my $real_sql = $SQL) =~ s/XX/$o_txt/gsm; + + my $sth = money::collections_tracker->db_Main->prepare($real_sql); + $sth->execute( + $org->id, $startdate, $enddate, + $org->id, $startdate, $enddate, + $org->id, $startdate, $enddate, + $org->id, $startdate, $enddate, + $org->id, $startdate, $enddate + ); + while (my $row = $sth->fetchrow_hashref) { $row->{usr} = actor::user->retrieve($row->{usr})->to_fieldmapper; $client->respond( $row ); diff --git a/Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm b/Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm index e9afe9d9d8..63b0ce287f 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm @@ -614,7 +614,7 @@ sub retrieve_record_transform { (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o; my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' ); - $_storage->connect; + #$_storage->connect; my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve', diff --git a/Open-ILS/src/perlmods/OpenILS/Const.pm b/Open-ILS/src/perlmods/OpenILS/Const.pm index 4e4b7e718e..46dadeb92b 100644 --- a/Open-ILS/src/perlmods/OpenILS/Const.pm +++ b/Open-ILS/src/perlmods/OpenILS/Const.pm @@ -74,6 +74,9 @@ econst OILS_UNLIMITED_CIRC_DURATION => 'unlimited'; # --------------------------------------------------------------------- econst OILS_SETTING_LOST_PROCESSING_FEE => 'circ.lost_materials_processing_fee'; econst OILS_SETTING_DEF_ITEM_PRICE => 'cat.default_item_price'; +econst OILS_SETTING_ORG_BOUNCED_EMAIL => 'org.bounced_emails'; +econst OILS_SETTING_CHARGE_LOST_ON_ZERO => 'circ.charge_lost_on_zero'; +econst OILS_SETTING_VOID_OVERDUE_ON_LOST => 'circ.void_overdue_on_lost'; diff --git a/Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm b/Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm index f0b4e42e1d..c12bab96a8 100644 --- a/Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm +++ b/Open-ILS/src/perlmods/OpenILS/Reporter/SQLBuilder.pm @@ -296,6 +296,23 @@ sub toSQL { return $self->{_sql} = $self->$toSQL; } +#------------------------------------------------------------------------------------------------- +package OpenILS::Reporter::SQLBuilder::Input::Transform::GenericTransform; + +sub toSQL { + my $self = shift; + my $func = $self->{transform}; + + my @params; + @params = @{ $self->{params} } if ($self->{params}); + + my $sql = $func . '(\''; + $sql .= join("','", @params) if (@params); + $sql .= '\')'; + + return $sql; +} + #------------------------------------------------------------------------------------------------- package OpenILS::Reporter::SQLBuilder::Input::Transform::NULL; @@ -728,7 +745,7 @@ package OpenILS::Reporter::SQLBuilder::Column::Transform::hour_trunc; sub toSQL { my $self = shift; - return 'DATE_TRUNC("' . $self->{_relation} . '"."' . $self->name . '")'; + return 'EXTRACT(HOUR FROM "' . $self->{_relation} . '"."' . $self->name . '")'; } sub is_aggregate { return 0 } diff --git a/Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm b/Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm index c3dc887d94..f2cf643059 100644 --- a/Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm +++ b/Open-ILS/src/perlmods/OpenILS/SIP/Patron.pm @@ -197,13 +197,13 @@ sub fee_amount { my $self = shift; syslog('LOG_DEBUG', 'OILS: Patron->fee_amount()'); - my $total = 0; - my $e = OpenILS::SIP->editor(); - my $xacts = $e->search_money_open_billable_transaction_summary( - { usr => $self->{user}->id, balance_owed => { '!=' => 0 } } ); + my $ses = $U->start_db_session(); + my $summary = $ses->request( + 'open-ils.storage.money.open_user_summary.search', $self->{user}->id )->gather(1); + $U->rollback_db_session($ses); - $total += $_->balance_owed for @$xacts; - syslog('LOG_INFO', "User ".$self->{user}->id." has a fee amount of \$$total"); + my $total = $summary->balance_owed; + syslog('LOG_INFO', "User ".$self->{id} .':'.$self->{user}->id." has a fee amount of \$$total"); return $total; } diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm b/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm index 9e11b20a22..fa4a142464 100644 --- a/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/CStoreEditor.pm @@ -196,6 +196,7 @@ sub xact_commit { # ----------------------------------------------------------------------------- sub xact_rollback { my $self = shift; + return unless $self->{session}; $self->log(I, "rolling back db session"); return $self->request($self->app.".transaction.rollback"); } @@ -208,12 +209,14 @@ sub xact_rollback { sub rollback { my $self = shift; $self->xact_rollback if $self->{xact}; + delete $self->{xact}; $self->disconnect; } sub disconnect { my $self = shift; $self->session->disconnect if $self->{session}; + delete $self->{session}; } @@ -268,7 +271,20 @@ sub request { } try { - $val = $self->session->request($method, @params)->gather(1); + + my $req = $self->session->request($method, @params); + + if( $self->substream ) { + $self->log(D,"running in substream mode"); + $val = []; + while( my $resp = $req->recv ) { + push(@$val, $resp->content) if $resp->content; + } + } else { + $val = $req->gather(1); + } + + #$val = $self->session->request($method, @params)->gather(1); } catch Error with { $err = shift; @@ -279,6 +295,12 @@ sub request { return $val; } +sub substream { + my( $self, $bool ) = @_; + $self->{substream} = $bool if defined $bool; + return $self->{substream}; +} + # ----------------------------------------------------------------------------- # Sets / Returns the requstor object. This is set when checkauth succeeds. @@ -434,6 +456,8 @@ sub __arg_to_string { sub runmethod { my( $self, $action, $type, $arg, $options ) = @_; + $options ||= {}; + if( $action eq 'retrieve' ) { if(! defined($arg) ) { $self->log(W,"$action $type called with no ID..."); @@ -449,13 +473,13 @@ sub runmethod { my $method = $self->app.".direct.$type.$action"; if( $action eq 'search' ) { - $method = "$method.atomic"; + $method .= '.atomic'; } elsif( $action eq 'batch_retrieve' ) { $action = 'search'; @arg = ( { id => $arg } ); $method =~ s/batch_retrieve/search/o; - $method = "$method.atomic"; + $method .= '.atomic'; } elsif( $action eq 'retrieve_all' ) { $action = 'search'; @@ -464,11 +488,13 @@ sub runmethod { $tt =~ s/\./::/og; my $fmobj = "Fieldmapper::$tt"; @arg = ( { $fmobj->Identity => { '!=' => undef } } ); - $method = "$method.atomic"; + $method .= '.atomic'; } $method =~ s/search/id_list/o if $options->{idlist}; + $method =~ s/\.atomic$//o if $self->substream($$options{substream} || 0); + # remove any stale events $self->clear_event; @@ -535,12 +561,16 @@ sub runmethod { return undef; } - if( $action eq 'search' or $action eq 'batch_retrieve' or $action eq 'retrieve_all') { + if( $action eq 'search' ) { $self->log(I, "$type.$action : returned ".scalar(@$obj). " result(s)"); $self->event(_mk_not_found($type, $arg)) unless @$obj; } - $arg->id($obj->id) if $action eq 'create'; # grabs the id on create + if( $action eq 'create' ) { + $self->log(I, "created a new $type object with ID " . $obj->id); + $arg->id($obj->id); + } + $self->data($obj); # cache the data for convenience return ($obj) ? $obj : 1; @@ -610,6 +640,31 @@ for my $object (keys %$map) { eval $retrieveallf; } +sub json_query { + my( $self, $arg, $options ) = @_; + $options ||= {}; + my @arg = ( ref($arg) eq 'ARRAY' ) ? @$arg : ($arg); + my $method = $self->app.'.json_query.atomic'; + $method =~ s/\.atomic$//o if $self->substream($$options{substream} || 0); + $self->clear_event; + my $obj; + my $err; + + try { + $obj = $self->request($method, @arg); + } catch Error with { $err = shift; }; + + if( $err ) { + $self->event( + OpenILS::Event->new( 'DATABASE_QUERY_FAILED', + payload => $arg, debug => "$err" )); + return undef; + } + + $self->log(I, "json_query : returned ".scalar(@$obj). " result(s)"); + return $obj; +} + 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/ModsParser.pm b/Open-ILS/src/perlmods/OpenILS/Utils/ModsParser.pm index 722cc42c93..f50b9e4c20 100644 --- a/Open-ILS/src/perlmods/OpenILS/Utils/ModsParser.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/ModsParser.pm @@ -49,21 +49,43 @@ my $xpathset = { author => { corporate => "//mods:mods/mods:name[\@type='corporate']/*[local-name()='namePart']". - "[../mods:role/mods:text[text()='creator']][1]", + "[../mods:role/mods:text[text()='creator']". + " or ../mods:role/mods:roleTerm[". + " \@type='text'". + " and \@authority='marcrelator'". + " and text()='creator']". + "][1]", personal => "//mods:mods/mods:name[\@type='personal']/*[local-name()='namePart']". - "[../mods:role/mods:text[text()='creator']][1]", + "[../mods:role/mods:text[text()='creator']". + " or ../mods:role/mods:roleTerm[". + " \@type='text'". + " and \@authority='marcrelator'". + " and text()='creator']". + "][1]", conference => "//mods:mods/mods:name[\@type='conference']/*[local-name()='namePart']". - "[../mods:role/mods:text[text()='creator']][1]", + "[../mods:role/mods:text[text()='creator']". + " or ../mods:role/mods:roleTerm[". + " \@type='text'". + " and \@authority='marcrelator'". + " and text()='creator']". + "][1]", other => "//mods:mods/mods:name[\@type='personal']/*[local-name()='namePart']", + any => + "//mods:mods/mods:name/*[local-name()='namePart'][1]", }, subject => { topic => - "//mods:mods/mods:subject/*[local-name()='geographic' or local-name()='name' or local-name()='temporal' or local-name()='topic']/parent::mods:subject", + "//mods:mods/mods:subject/*[". + " local-name()='geographic'". + " or local-name()='name'". + " or local-name()='temporal'". + " or local-name()='topic'". + "]/parent::mods:subject", # geographic => # "//mods:mods/*[local-name()='subject']/*[local-name()='geographic']", @@ -253,9 +275,10 @@ sub mods_values_to_mods_slim { if(!$tmp) { $author = ""; } else { ($author = $tmp->{personal}) || - ($author = $tmp->{other}) || ($author = $tmp->{corporate}) || - ($author = $tmp->{conference}); + ($author = $tmp->{conference}) || + ($author = $tmp->{other}) || + ($author = $tmp->{any}); } $tmp = $modsperl->{subject}; @@ -403,8 +426,10 @@ sub finish_mods_batch { my $record = init_virtual_record(); # turn the hash into a fieldmapper object - (my $title = $perl->{title}) =~ s/\[.*?\]//og; - (my $author = $perl->{author}) =~ s/\(.*?\)//og; + #(my $title = $perl->{title}) =~ s/\[.*?\]//og; + #(my $author = $perl->{author}) =~ s/\(.*?\)//og; + my $title = $perl->{title}; + my $author = $perl->{author}; my @series; for my $s (@{$perl->{series}}) { diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/PermitHold.pm b/Open-ILS/src/perlmods/OpenILS/Utils/PermitHold.pm index 4fd43ce681..5f69de9dff 100644 --- a/Open-ILS/src/perlmods/OpenILS/Utils/PermitHold.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/PermitHold.pm @@ -69,8 +69,21 @@ sub permit_copy_hold { push( @allevents, OpenILS::Event->new('ITEM_NOT_HOLDABLE') ) unless $U->is_true($ctx->{copy}->status->holdable); - my $evt = check_age_protect($ctx->{patron}, $ctx->{copy}); - push( @allevents, $evt ) if $evt; + my $evt; + + # grab the data safely + my $rlib = ref($$params{request_lib}) ? $$params{request_lib}->id : $$params{request_lib}; + my $olib = ref($ctx->{volume}) ? $ctx->{volume}->owning_lib : -1; + my $rid = ref($ctx->{requestor}) ? $ctx->{requestor}->id : -2; + my $pid = ($params->{patron}) ? $params->{patron}->id : $params->{patron_id}; + + if( ($rid ne $pid) and ($olib eq $rlib) ) { + $logger->info("Item owning lib $olib is the same as the request lib. No age_protection will be checked"); + } else { + $logger->info("item owning lib = $olib, request lib = $rlib, requestor=$rid, patron=$pid. checking age_protection"); + $evt = check_age_protect($ctx->{patron}, $ctx->{copy}); + push( @allevents, $evt ) if $evt; + } $logger->debug("Running permit_copy_hold on copy " . $$params{copy}->id); @@ -82,7 +95,6 @@ sub permit_copy_hold { # Extract and uniquify the event list # -------------------------------------------------------------- my $events = $result->{events}; - my $pid = ($params->{patron}) ? $params->{patron}->id : $params->{patron_id}; $logger->debug("circ_permit_hold for user $pid returned events: [@$events]"); push( @allevents, OpenILS::Event->new($_)) for @$events; diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent.pm b/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent.pm index 18aec2a2b1..b7b94f18c0 100644 --- a/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent.pm +++ b/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent.pm @@ -30,13 +30,22 @@ sub import { } +my $net_timeout; +my $cache; sub child_init { OpenSRF::System->bootstrap_client( config_file => $bs_config ); my $sclient = OpenSRF::Utils::SettingsClient->new(); my $ac_data = $sclient->config_value("added_content"); + + return unless $ac_data; + + $cache = OpenSRF::Utils::Cache->new; + my $ac_handler = $ac_data->{module}; + $net_timeout = $ac_data->{timeout} || 3; + return unless $ac_handler; $logger->debug("Attempting to load Added Content handler: $ac_handler"); @@ -62,6 +71,13 @@ sub handler { child_init() unless $handler; # why isn't apache doing this for us? return Apache2::Const::NOT_FOUND unless $handler; + # if this memcache key is set, added content lookups are disabled + if( $cache->get_cache('ac.no_lookup') ) { + $logger->info("added content lookup disabled"); + return Apache2::Const::NOT_FOUND; + } + + my( undef, $data, $format, $key ) = split(/\//, $r->path_info); my $err; @@ -81,5 +97,20 @@ sub handler { +# generic GET call +sub get_url { + my( $self, $url ) = @_; + $logger->info("added content getting [timeout=$net_timeout] URL = $url"); + my $agent = LWP::UserAgent->new(timeout => $net_timeout); + my $res = $agent->get($url); + die "added content request failed: " . $res->status_line ."\n" unless $res->is_success; + return $res->content; +} + + + + + + 1; diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent/Syndetic.pm b/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent/Syndetic.pm index 6e09b264e1..80a294a9e6 100644 --- a/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent/Syndetic.pm +++ b/Open-ILS/src/perlmods/OpenILS/WWW/AddedContent/Syndetic.pm @@ -5,6 +5,7 @@ use OpenSRF::Utils::Logger qw/$logger/; use OpenSRF::Utils::SettingsParser; use JSON; use OpenSRF::EX qw/:try/; +use OpenILS::WWW::AddedContent; @@ -223,12 +224,9 @@ sub fetch_content { my( $self, $page, $key ) = @_; my $uname = $self->userid; my $url = $self->base_url . "?isbn=$key/$page&client=$uname&type=rw12"; - $logger->info("added content URL = $url"); - my $agent = LWP::UserAgent->new; - my $res = $agent->get($url); - die "added content request failed: " . $res->status_line ."\n" unless $res->is_success; - return $res->content; + return OpenILS::WWW::AddedContent->get_url($url); } + 1; diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/SuperCat.pm b/Open-ILS/src/perlmods/OpenILS/WWW/SuperCat.pm index 4c6f76e819..d131b30dda 100644 --- a/Open-ILS/src/perlmods/OpenILS/WWW/SuperCat.pm +++ b/Open-ILS/src/perlmods/OpenILS/WWW/SuperCat.pm @@ -1,7 +1,6 @@ package OpenILS::WWW::SuperCat; use strict; use warnings; -use Apache2 (); use Apache2::Log; use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log); use APR::Const -compile => qw(:error SUCCESS); @@ -831,6 +830,8 @@ Content-type: application/opensearchdescription+xml; charset=utf-8 template="$base/1.1/$lib/mods/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/> + Search $lib Mike Rylander for GPLS/PINES @@ -984,6 +985,8 @@ sub opensearch_feed { } } + $lang = 'eng' if ($lang eq 'en-US'); + if ($term_copy) { no warnings; $class = 'keyword' if ($class eq '-'); diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/XMLRPCGateway.pm b/Open-ILS/src/perlmods/OpenILS/WWW/XMLRPCGateway.pm index 4679e44fda..330b22f113 100644 --- a/Open-ILS/src/perlmods/OpenILS/WWW/XMLRPCGateway.pm +++ b/Open-ILS/src/perlmods/OpenILS/WWW/XMLRPCGateway.pm @@ -80,10 +80,24 @@ sub handler { sub run_request { - my( $service, $method, @args ) = @_; - my $ses = OpenSRF::AppSession->create( $service ); - my $data = $ses->request($method, @args)->gather(1); - return wrap_perl($data); + my( $service, $method, @args ) = @_; + my $ses = OpenSRF::AppSession->create( $service ); + #my $data = $ses->request($method, @args)->gather(1); + + my $data = []; + my $req = $ses->request($method, @args); + while( my $resp = $req->recv( timeout => 600 ) ) { + if( $req->failed ) { + push( @$data, $req->failed ); + last; + } + push( @$data, $resp->content ); + } + + return [] if scalar(@$data) == 0; + return wrap_perl($$data[0]) + if scalar(@$data) == 1 and $method !~ /.atomic$/og; + return wrap_perl($data); } # These should probably be moved out to a library somewhere diff --git a/Open-ILS/src/reporter/clark-kent.pl b/Open-ILS/src/reporter/clark-kent.pl index 0624b23bfb..206f1e8fad 100755 --- a/Open-ILS/src/reporter/clark-kent.pl +++ b/Open-ILS/src/reporter/clark-kent.pl @@ -71,7 +71,7 @@ if ($daemon) { open(F, ">$lockfile"); print F $$; close F; - daemonize("Clark Kent, waiting for trouble"); + daemonize("clark_master"); } diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql index b180840292..294bb04905 100644 --- a/Open-ILS/src/sql/Pg/005.schema.actors.sql +++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql @@ -133,8 +133,8 @@ CREATE TRIGGER actor_crypt_pw_insert_trigger CREATE RULE protect_user_delete AS ON DELETE TO actor.usr DO INSTEAD UPDATE actor.usr SET deleted = TRUE WHERE OLD.id = actor.usr.id; -- Just so that there is a user... -INSERT INTO actor.usr ( profile, card, usrname, passwd, first_given_name, family_name, dob, master_account, super_user, ident_type, ident_value, home_ou ) - VALUES ( 1, 1,'admin', 'open-ils', 'Administrator', 'System Account', '1979-01-22', TRUE, TRUE, 1, 'identification', 1 ); +INSERT INTO actor.usr ( profile, card, usrname, passwd, first_given_name, family_name, dob, master_account, super_user, ident_type, ident_value, home_ou ) + VALUES ( 1, 1, 'admin', 'open-ils', 'Administrator', 'System Account', '1979-01-22', TRUE, TRUE, 1, 'identification', 1 ); CREATE TABLE actor.usr_note ( id BIGSERIAL PRIMARY KEY, @@ -375,7 +375,8 @@ CREATE TABLE actor.org_unit ( shortname TEXT NOT NULL, name TEXT NOT NULL, email TEXT, - phone TEXT + phone TEXT, + opac_visible BOOL NOT NULL DEFAULT TRUE ); CREATE INDEX actor_org_unit_parent_ou_idx ON actor.org_unit (parent_ou); CREATE INDEX actor_org_unit_ou_type_idx ON actor.org_unit (ou_type); diff --git a/Open-ILS/src/sql/Pg/006.schema.permissions.sql b/Open-ILS/src/sql/Pg/006.schema.permissions.sql index 39293b46aa..55dc5fc241 100644 --- a/Open-ILS/src/sql/Pg/006.schema.permissions.sql +++ b/Open-ILS/src/sql/Pg/006.schema.permissions.sql @@ -11,19 +11,22 @@ CREATE TABLE permission.perm_list ( CREATE INDEX perm_list_code_idx ON permission.perm_list (code); INSERT INTO permission.perm_list VALUES (-1, 'EVERYTHING', NULL); -/* INSERT INTO permission.perm_list VALUES (2, 'OPAC_LOGIN', NULL); INSERT INTO permission.perm_list VALUES (4, 'STAFF_LOGIN', NULL); INSERT INTO permission.perm_list VALUES (5, 'MR_HOLDS', NULL); INSERT INTO permission.perm_list VALUES (6, 'TITLE_HOLDS', NULL); INSERT INTO permission.perm_list VALUES (7, 'VOLUME_HOLDS', NULL); +INSERT INTO permission.perm_list VALUES (8, 'COPY_HOLDS', 'User is allowed to place a hold on a specific copy'); INSERT INTO permission.perm_list VALUES (9, 'REQUEST_HOLDS', NULL); INSERT INTO permission.perm_list VALUES (10, 'REQUEST_HOLDS_OVERRIDE', NULL); +INSERT INTO permission.perm_list VALUES (11, 'VIEW_HOLD', 'Allows a user to view another user''s holds'); INSERT INTO permission.perm_list VALUES (13, 'DELETE_HOLDS', NULL); +INSERT INTO permission.perm_list VALUES (14, 'UPDATE_HOLD', 'Allows a user to update another user''s hold'); INSERT INTO permission.perm_list VALUES (15, 'RENEW_CIRC', NULL); INSERT INTO permission.perm_list VALUES (16, 'VIEW_USER_FINES_SUMMARY', NULL); INSERT INTO permission.perm_list VALUES (17, 'VIEW_USER_TRANSACTIONS', NULL); INSERT INTO permission.perm_list VALUES (18, 'UPDATE_MARC', NULL); +INSERT INTO permission.perm_list VALUES (19, 'CREATE_MARC', 'User is allowed to create new MARC records'); INSERT INTO permission.perm_list VALUES (20, 'IMPORT_MARC', NULL); INSERT INTO permission.perm_list VALUES (21, 'CREATE_VOLUME', NULL); INSERT INTO permission.perm_list VALUES (22, 'UPDATE_VOLUME', NULL); @@ -47,13 +50,9 @@ INSERT INTO permission.perm_list VALUES (41, 'CREATE_TRANSACTION', 'User may cre INSERT INTO permission.perm_list VALUES (43, 'CREATE_BILL', 'Allows a user to create a new bill on a transaction'); INSERT INTO permission.perm_list VALUES (44, 'VIEW_CONTAINER', 'Allows a user to view another user''s containers (buckets)'); INSERT INTO permission.perm_list VALUES (45, 'CREATE_CONTAINER', 'Allows a user to create a new container for another user'); -INSERT INTO permission.perm_list VALUES (8, 'COPY_HOLDS', 'User is allowed to place a hold on a specific copy'); INSERT INTO permission.perm_list VALUES (24, 'CREATE_COPY', 'User is allowed to create a new copy object'); -INSERT INTO permission.perm_list VALUES (19, 'CREATE_ORIGINAL_MARC', 'User is allowed to create new MARC records'); INSERT INTO permission.perm_list VALUES (47, 'UPDATE_ORG_UNIT', 'Allows a user to change org unit settings'); INSERT INTO permission.perm_list VALUES (48, 'VIEW_CIRCULATIONS', 'Allows a user to see what another use has checked out'); -INSERT INTO permission.perm_list VALUES (14, 'UPDATE_HOLD', 'Allows a user to update another user''s hold'); -INSERT INTO permission.perm_list VALUES (11, 'VIEW_HOLD', 'Allows a user to view another user''s holds'); INSERT INTO permission.perm_list VALUES (42, 'VIEW_TRANSACTION', 'User may view another user''s transactions'); INSERT INTO permission.perm_list VALUES (49, 'DELETE_CONTAINER', 'Allows a user to delete another user container'); INSERT INTO permission.perm_list VALUES (50, 'CREATE_CONTAINER_ITEM', 'Create a container item for another user'); @@ -111,9 +110,55 @@ INSERT INTO permission.perm_list VALUES (101, 'OFFLINE_EXECUTE', 'Allows a user INSERT INTO permission.perm_list VALUES (102, 'CIRC_OVERRIDE_DUE_DATE', 'Allows a user to change set the due date on an item to any date'); INSERT INTO permission.perm_list VALUES (103, 'CIRC_PERMIT_OVERRIDE', 'Allows a user to bypass the circ permit call for checkout'); INSERT INTO permission.perm_list VALUES (104, 'COPY_IS_REFERENCE.override', 'Allows a user to override the copy_is_reference event'); - -SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 105); -*/ +INSERT INTO permission.perm_list VALUES (105, 'VOID_BILLING', 'Allows a user to void a bill'); +INSERT INTO permission.perm_list VALUES (106, 'CIRC_CLAIMS_RETURNED.override', 'Allows a person to check in/out an item that is claims returned'); +INSERT INTO permission.perm_list VALUES (107, 'COPY_BAD_STATUS.override', 'Allows a user to check out an item in a non-circulatable status'); +INSERT INTO permission.perm_list VALUES (108, 'COPY_ALERT_MESSAGE.override', 'Allows a user to check in/out an item that has an alert message'); +INSERT INTO permission.perm_list VALUES (109, 'COPY_STATUS_LOST.override', 'Allows a user to remove the lost status from a copy'); +INSERT INTO permission.perm_list VALUES (110, 'COPY_STATUS_MISSING.override', 'Allows a user to change the missing status on a copy'); +INSERT INTO permission.perm_list VALUES (111, 'ABORT_TRANSIT', 'Allows a user to abort a copy transit if the user is at the transit destination or source'); +INSERT INTO permission.perm_list VALUES (112, 'ABORT_REMOTE_TRANIST', 'Allows a user to abort a copy transit if the user is not at the transit source or dest'); +INSERT INTO permission.perm_list VALUES (113, 'VIEW_ZIP_DATA', 'Allowsa user to query the zip code data method'); +INSERT INTO permission.perm_list VALUES (114, 'CANCEL_HOLDS', ''); +INSERT INTO permission.perm_list VALUES (115, 'CREATE_DUPLICATE_HOLDS', 'Allows a user to create duplicate holds (e.g. two holds on the same title)'); +INSERT INTO permission.perm_list VALUES (117, 'actor.org_unit.closed_date.update', 'Allows a user to update a closed date interval for a given location'); +INSERT INTO permission.perm_list VALUES (116, 'actor.org_unit.closed_date.delete', 'Allows a user to remove a closed date interval for a given location'); +INSERT INTO permission.perm_list VALUES (118, 'actor.org_unit.closed_date.create', 'Allows a user to create a new closed date for a location'); +INSERT INTO permission.perm_list VALUES (119, 'DELETE_NON_CAT_TYPE', 'Allows a user to delete a non cataloged type'); +INSERT INTO permission.perm_list VALUES (120, 'money.collections_tracker.create', 'Allows a user to put someone into collections'); +INSERT INTO permission.perm_list VALUES (121, 'money.collections_tracker.delete', 'Allows a user to remove someone from collections'); +INSERT INTO permission.perm_list VALUES (122, 'BAR_PATRON', 'Allows a user to bar a patron'); +INSERT INTO permission.perm_list VALUES (123, 'UNBAR_PATRON', 'Allows a user to un-bar a patron'); +INSERT INTO permission.perm_list VALUES (124, 'DELETE_WORKSTATION', 'Allows a user to remove an existing workstation so a new one can replace it'); +INSERT INTO permission.perm_list VALUES (125, 'group_application.user', 'Allows a user to add/remove users to/from the "User" group'); +INSERT INTO permission.perm_list VALUES (126, 'group_application.user.patron', 'Allows a user to add/remove users to/from the "Patron" group'); +INSERT INTO permission.perm_list VALUES (127, 'group_application.user.staff', 'Allows a user to add/remove users to/from the "Staff" group'); +INSERT INTO permission.perm_list VALUES (128, 'group_application.user.staff.circ', 'Allows a user to add/remove users to/from the "Circulator" group'); +INSERT INTO permission.perm_list VALUES (129, 'group_application.user.staff.cat', 'Allows a user to add/remove users to/from the "Cataloger" group'); +INSERT INTO permission.perm_list VALUES (130, 'group_application.user.staff.admin.global_admin', 'Allows a user to add/remove users to/from the "GlobalAdmin" group'); +INSERT INTO permission.perm_list VALUES (131, 'group_application.user.staff.admin.local_admin', 'Allows a user to add/remove users to/from the "LocalAdmin" group'); +INSERT INTO permission.perm_list VALUES (132, 'group_application.user.staff.admin.lib_manager', 'Allows a user to add/remove users to/from the "LibraryManager" group'); +INSERT INTO permission.perm_list VALUES (133, 'group_application.user.staff.cat.cat1', 'Allows a user to add/remove users to/from the "Cat1" group'); +INSERT INTO permission.perm_list VALUES (134, 'group_application.user.staff.supercat', 'Allows a user to add/remove users to/from the "Supercat" group'); +INSERT INTO permission.perm_list VALUES (135, 'group_application.user.sip_client', 'Allows a user to add/remove users to/from the "SIP-Client" group'); +INSERT INTO permission.perm_list VALUES (136, 'group_application.user.vendor', 'Allows a user to add/remove users to/from the "Vendor" group'); +INSERT INTO permission.perm_list VALUES (137, 'ITEM_AGE_PROTECTED.override', 'Allows a user to place a hold on an age-protected item'); +INSERT INTO permission.perm_list VALUES (138, 'MAX_RENEWALS_REACHED.override', 'Allows a user to renew an item past the maximun renewal count'); +INSERT INTO permission.perm_list VALUES (139, 'PATRON_EXCEEDS_CHECKOUT_COUNT.override', 'Allow staff to override checkout count failure'); +INSERT INTO permission.perm_list VALUES (140, 'PATRON_EXCEEDS_OVERDUE_COUNT.override', 'Allow staff to override overdue count failure'); +INSERT INTO permission.perm_list VALUES (141, 'PATRON_EXCEEDS_FINES.override', 'Allow staff to override fine amount checkout failure'); +INSERT INTO permission.perm_list VALUES (142, 'CIRC_EXCEEDS_COPY_RANGE.override', ''); +INSERT INTO permission.perm_list VALUES (143, 'ITEM_ON_HOLDS_SHELF.override', ''); +INSERT INTO permission.perm_list VALUES (144, 'COPY_NOT_AVAILABLE.override', 'Allow staff to force checkout of Missing/Lost type items'); +INSERT INTO permission.perm_list VALUES (145, 'VOLUME_UPDATE', ''); +INSERT INTO permission.perm_list VALUES (146, 'HOLD_EXISTS.override', 'allows users to place multiple holds on a single title'); +INSERT INTO permission.perm_list VALUES (147, 'RUN_REPORTS', 'Allows a users to run reports'); +INSERT INTO permission.perm_list VALUES (148, 'SHARE_REPORT_FOLDER', 'Allows a user to share report his own folders'); +INSERT INTO permission.perm_list VALUES (149, 'VIEW_REPORT_OUTPUT', 'Allow user to view report output'); +INSERT INTO permission.perm_list VALUES (150, 'COPY_CIRC_NOT_ALLOWED.override', 'Allows a user to checkout an item that is marked as non-circ'); +INSERT INTO permission.perm_list VALUES (151, 'DELETE_CONTAINER_ITEM', 'Allows a user to delete an item out of another user''s container'); + +SELECT SETVAL('permission.perm_list_id_seq'::TEXT, 151, TRUE); CREATE TABLE permission.grp_tree ( id SERIAL PRIMARY KEY, @@ -126,16 +171,20 @@ CREATE TABLE permission.grp_tree ( ); CREATE INDEX grp_tree_parent_idx ON permission.grp_tree (parent); -/* -INSERT INTO permission.grp_tree VALUES (1, 'Users', NULL, NULL, '3 years'); -INSERT INTO permission.grp_tree VALUES (2, 'Patrons', 1, NULL, '3 years'); -INSERT INTO permission.grp_tree VALUES (3, 'Staff', 1, NULL, '3 years'); -INSERT INTO permission.grp_tree VALUES (4, 'Catalogers', 3, NULL, '3 years'); -INSERT INTO permission.grp_tree VALUES (5, 'Circulators', 3, NULL, '3 years'); -INSERT INTO permission.grp_tree VALUES (10, 'Local System Administrator', 3, 'System maintenance, configuration, etc.', '3 years'); +INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) + VALUES (1, 'Users', NULL, NULL, '3 years', FALSE, 'group_application.user'); +INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) + VALUES (2, 'Patrons', 1, NULL, '3 years', TRUE, 'group_application.user.patron'); +INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) + VALUES (3, 'Staff', 1, NULL, '3 years', FALSE, 'group_application.user.staff'); +INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) + VALUES (4, 'Catalogers', 3, NULL, '3 years', TRUE, 'group_application.user.staff.cat'); +INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) + VALUES (5, 'Circulators', 3, NULL, '3 years', TRUE, 'group_application.user.staff.circ'); +INSERT INTO permission.grp_tree (id, name, parent, description, perm_interval, usergroup, application_perm) + VALUES (10, 'Local System Administrator', 3, 'System maintenance, configuration, etc.', '3 years', TRUE, 'group_application.user.staff.admin.local_admin'); SELECT SETVAL('permission.grp_tree_id_seq'::TEXT, 11); -*/ CREATE TABLE permission.grp_perm_map ( id SERIAL PRIMARY KEY, @@ -146,7 +195,7 @@ CREATE TABLE permission.grp_perm_map ( CONSTRAINT perm_grp_once UNIQUE (grp,perm) ); -/* +-- XXX Incomplete base permission setup. A patch would be appreciated. INSERT INTO permission.grp_perm_map VALUES (57, 2, 15, 0, false); INSERT INTO permission.grp_perm_map VALUES (109, 2, 95, 0, false); INSERT INTO permission.grp_perm_map VALUES (1, 1, 2, 0, false); @@ -258,7 +307,6 @@ INSERT INTO permission.grp_perm_map VALUES (133, 5, 102, 0, false); INSERT INTO permission.grp_perm_map VALUES (138, 5, 104, 1, false); SELECT SETVAL('permission.grp_perm_map_id_seq'::TEXT, 139); -*/ CREATE TABLE permission.usr_perm_map ( id SERIAL PRIMARY KEY, diff --git a/Open-ILS/src/sql/Pg/080.schema.money.sql b/Open-ILS/src/sql/Pg/080.schema.money.sql index 6db570b77a..fe974c5562 100644 --- a/Open-ILS/src/sql/Pg/080.schema.money.sql +++ b/Open-ILS/src/sql/Pg/080.schema.money.sql @@ -26,6 +26,8 @@ CREATE TABLE money.grocery ( -- Catchall table for local billing note TEXT ) INHERITS (money.billable_xact); ALTER TABLE money.grocery ADD PRIMARY KEY (id); +CREATE INDEX circ_open_date_idx ON "money".grocery (xact_start) WHERE xact_finish IS NULL; +CREATE INDEX m_g_usr_idx ON "money".grocery (usr); CREATE TABLE money.billing ( id BIGSERIAL PRIMARY KEY, @@ -321,6 +323,7 @@ CREATE TABLE money.bnm_desk_payment ( ) INHERITS (money.bnm_payment); ALTER TABLE money.bnm_desk_payment ADD PRIMARY KEY (id); + CREATE OR REPLACE VIEW money.desk_payment_view AS SELECT p.*,c.relname AS payment_type FROM money.bnm_desk_payment p @@ -363,6 +366,12 @@ CREATE INDEX money_credit_card_payment_ts_idx ON money.credit_card_payment (paym CREATE INDEX money_credit_card_payment_accepting_usr_idx ON money.credit_card_payment (accepting_usr); CREATE INDEX money_credit_card_payment_cash_drawer_idx ON money.credit_card_payment (cash_drawer); +CREATE OR REPLACE VIEW money.non_drawer_payment_view AS + SELECT p.*, c.relname AS payment_type + FROM money.bnm_payment p + JOIN pg_class c ON p.tableoid = c.oid + WHERE c.relname NOT IN ('cash_payment','check_payment','credit_card_payment'); + CREATE OR REPLACE VIEW money.cashdrawer_payment_view AS SELECT ou.id AS org_unit, ws.id AS cashdrawer, diff --git a/Open-ILS/src/sql/Pg/090.schema.action.sql b/Open-ILS/src/sql/Pg/090.schema.action.sql index c3fc5ca1d3..866f8f749f 100644 --- a/Open-ILS/src/sql/Pg/090.schema.action.sql +++ b/Open-ILS/src/sql/Pg/090.schema.action.sql @@ -105,6 +105,8 @@ ALTER TABLE action.circulation ADD PRIMARY KEY (id); CREATE INDEX circ_open_xacts_idx ON action.circulation (usr) WHERE xact_finish IS NULL; CREATE INDEX circ_outstanding_idx ON action.circulation (usr) WHERE checkin_time IS NULL; CREATE INDEX circ_checkin_time ON "action".circulation (checkin_time) WHERE checkin_time IS NOT NULL; +CREATE INDEX circ_circ_lib_idx ON "action".circulation (circ_lib); +CREATE INDEX circ_open_date_idx ON "action".circulation (xact_start) WHERE xact_finish IS NULL; CREATE OR REPLACE VIEW action.open_circulation AS diff --git a/Open-ILS/src/sql/Pg/reporter-schema.sql b/Open-ILS/src/sql/Pg/reporter-schema.sql index 8990e46188..8e1684c457 100644 --- a/Open-ILS/src/sql/Pg/reporter-schema.sql +++ b/Open-ILS/src/sql/Pg/reporter-schema.sql @@ -139,20 +139,20 @@ SELECT r.id, r.tcn_source, r.tcn_value, title.value AS title, - author.value AS author, + FIRST(author.value) AS author, publisher.value AS publisher, SUBSTRING(pubdate.value FROM $$\d+$$) AS pubdate, ARRAY_ACCUM( SUBSTRING(isbn.value FROM $$^\S+$$) ) AS isbn, ARRAY_ACCUM( SUBSTRING(issn.value FROM $$^\S+$$) ) AS issn FROM biblio.record_entry r LEFT JOIN metabib.full_rec title ON (r.id = title.record AND title.tag = '245' AND title.subfield = 'a') - LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag = '100' AND author.subfield = 'a') + LEFT JOIN metabib.full_rec author ON (r.id = author.record AND author.tag IN ('100','110','111') AND author.subfield = 'a') LEFT JOIN metabib.full_rec publisher ON (r.id = publisher.record AND publisher.tag = '260' AND publisher.subfield = 'b') LEFT JOIN metabib.full_rec pubdate ON (r.id = pubdate.record AND pubdate.tag = '260' AND pubdate.subfield = 'c') LEFT JOIN metabib.full_rec isbn ON (r.id = isbn.record AND isbn.tag IN ('024', '020') AND isbn.subfield IN ('a','z')) LEFT JOIN metabib.full_rec issn ON (r.id = issn.record AND issn.tag = '022' AND issn.subfield = 'a') WHERE r.deleted IS FALSE - GROUP BY 1,2,3,4,5,6,7,8,9; + GROUP BY 1,2,3,4,5,6,8,9; CREATE OR REPLACE VIEW reporter.demographic AS SELECT u.id, @@ -174,5 +174,37 @@ SELECT id, END AS "type" FROM action.circulation; +CREATE OR REPLACE VIEW reporter.hold_request_record AS +SELECT id, + target, + hold_type, + CASE + WHEN hold_type = 'T' + THEN target + WHEN hold_type = 'V' + THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = ahr.target) + WHEN hold_type = 'C' + THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = ahr.target) + WHEN hold_type = 'M' + THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = ahr.target) + END AS bib_record + FROM action.hold_request ahr; + +CREATE OR REPLACE VIEW reporter.xact_billing_totals AS +SELECT b.xact, + SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided, + SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided, + SUM( amount ) as total + FROM money.billing b + GROUP BY 1; + +CREATE OR REPLACE VIEW reporter.xact_paid_totals AS +SELECT b.xact, + SUM( CASE WHEN b.voided THEN 0 ELSE amount END ) as unvoided, + SUM( CASE WHEN b.voided THEN amount ELSE 0 END ) as voided, + SUM( amount ) as total + FROM money.payment b + GROUP BY 1; + COMMIT; diff --git a/Open-ILS/src/support-scripts/ac_ctl.pl b/Open-ILS/src/support-scripts/ac_ctl.pl new file mode 100755 index 0000000000..3b260b8450 --- /dev/null +++ b/Open-ILS/src/support-scripts/ac_ctl.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl +use strict; use warnings; + +use OpenSRF::AppSession; +use OpenSRF::System; +use OpenSRF::Utils::SettingsClient; +use OpenSRF::Utils::Cache; + +my $config = shift; +my $command = shift; +die < [enable|disable] + +USAGE + unless $command; + +OpenSRF::System->bootstrap_client(config_file => $config); + +my $cache = OpenSRF::Utils::Cache->new; +$cache->put_cache('ac.no_lookup', 1) if $command eq 'disable'; +$cache->delete_cache('ac.no_lookup') if $command eq 'enable'; + + diff --git a/Open-ILS/src/support-scripts/marc_export b/Open-ILS/src/support-scripts/marc_export new file mode 100755 index 0000000000..e7dda64737 --- /dev/null +++ b/Open-ILS/src/support-scripts/marc_export @@ -0,0 +1,87 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use OpenSRF::System; +use OpenSRF::EX qw/:try/; +use OpenSRF::AppSession; +use OpenSRF::Utils::SettingsClient; +use OpenILS::Application::AppUtils; +use OpenILS::Utils::Fieldmapper; + +use MARC::Record; +use MARC::File::XML; +use UNIVERSAL::require; + +use Getopt::Long; + +my @formats = qw/USMARC UNIMARC XML/; + +my ($config,$format,$encoding,$help) = ('/openils/conf/bootstrap.conf','USMARC','MARC8'); + +GetOptions( + 'help' => \$help, + 'config=s' => \$config, + 'format=s' => \$format, + 'encoding=s' => \$encoding, +); + +if ($help) { + print <<" HELP"; +Usage: $0 [options] + --help or -h This screen. + --config or -c Configuration file [/openils/conf/bootstrap.conf] + --format or -f Output format (USMARC, UNIMARC, XML) [USMARC] + --encoding or -e Output Encoding (UTF-8, ISO-8859-?, MARC8) [MARC8] + +Example: + + cat list_of_ids | $0 > output_file + + HELP + exit; +} + +$format = uc($format); +$encoding = uc($encoding); + +binmode(STDOUT, ':raw') if ($encoding ne 'UTF-8'); +binmode(STDOUT, ':utf8') if ($encoding eq 'UTF-8'); + +if (!grep { $format eq $_ } @formats) { + die "Please select a supported format. ". + "Right now that means one of [". + join('|',@formats). "]\n"; +} + +if ($format ne 'XML') { + my $type = 'MARC::File::' . $format; + $type->require; +} + +OpenSRF::System->bootstrap_client( config_file => $config ); +Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL")); + +my $ses = OpenSRF::AppSession->connect('open-ils.cstore'); + +print <
+ +HEADER + +while ( my $i = <> ) { + my $bib = $ses->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve', $i )->gather(1); + + next unless $bib; + + if ($format eq 'XML') { + print $bib->marc . "\n"; + } else { + print MARC::Record->new_from_xml( $bib->marc, $encoding, $format )->as_usmarc; + } +} + +print "\n" if ($format eq 'XML'); + +$ses->disconnect; + diff --git a/Open-ILS/src/templates/marc/k_audio.xml b/Open-ILS/src/templates/marc/k_audio.xml new file mode 100644 index 0000000000..22318e0832 --- /dev/null +++ b/Open-ILS/src/templates/marc/k_audio.xml @@ -0,0 +1,54 @@ + + 01149cjm a2200265Ka 4500 + 070101s eng d + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/templates/marc/k_book.xml b/Open-ILS/src/templates/marc/k_book.xml new file mode 100644 index 0000000000..01d8266cbe --- /dev/null +++ b/Open-ILS/src/templates/marc/k_book.xml @@ -0,0 +1,44 @@ + + 00620cam a2200205Ka 4500 + 070101s eng d + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/templates/marc/k_video.xml b/Open-ILS/src/templates/marc/k_video.xml new file mode 100644 index 0000000000..d392a3cf79 --- /dev/null +++ b/Open-ILS/src/templates/marc/k_video.xml @@ -0,0 +1,44 @@ + + 01490cgm a2200229Ka 4500 + 070101s eng d + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/web/opac/common/js/DP_DateExtensions.js b/Open-ILS/web/opac/common/js/DP_DateExtensions.js new file mode 100644 index 0000000000..fd602c63c5 --- /dev/null +++ b/Open-ILS/web/opac/common/js/DP_DateExtensions.js @@ -0,0 +1,359 @@ +/* DepressedPress.com DP_DateExtensions +Author: Jim Davis, the Depressed Press of Boston +Date: June 20, 2006 +Contact: webmaster@depressedpress.com +Website: www.depressedpress.com + +Full documentation can be found at: +http://www.depressedpress.com/Content/Development/JavaScript/Extensions/ + +DP_DateExtensions adds features to the JavaScript "Date" datatype. +Copyright (c) 1996-2006, The Depressed Press of Boston (depressedpress.com) +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + ++) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. ++) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. ++) Neither the name of the DEPRESSED PRESS OF BOSTON (DEPRESSEDPRESS.COM) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +CHANGES: -------------------------------------------------------------------------------- + + 2007-02-02 / billserickson@gmail.com + - chopped out some utility methods to trim file size + - changed some formatting for visual ease + - date / time can now be separated by a "T", "t" or a space + - truncating milliseconds. not needed and .123456 + comes accross 123456ms and not 123ms + 456 microseconds +*/ + + +Date.parseIso8601 = function(CurDate) { + + // Check the input parameters + if ( typeof CurDate != "string" ) { + return null; + }; + // Set the fragment expressions + var S = "[\\-/:.]"; + var Yr = "((?:1[6-9]|[2-9][0-9])[0-9]{2})"; + var Mo = S + "((?:1[012])|(?:0[1-9])|[1-9])"; + var Dy = S + "((?:3[01])|(?:[12][0-9])|(?:0[1-9])|[1-9])"; + var Hr = "(2[0-4]|[01]?[0-9])"; + var Mn = S + "([0-5]?[0-9])"; + var Sd = "(?:" + S + "([0-5]?[0-9])(?:[.,]([0-9]+))?)?"; + var TZ = "(?:(Z)|(?:([\+\-])(1[012]|[0]?[0-9])(?::?([0-5]?[0-9]))?))?"; + // RegEx the input + // First check: Just date parts (month and day are optional) + // Second check: Full date plus time (seconds, milliseconds and TimeZone info are optional) + var TF; + + if ( TF = new RegExp("^" + Yr + "(?:" + Mo + "(?:" + Dy + ")?)?" + "$").exec(CurDate) ) { + } else if ( TF = new RegExp("^" + Yr + Mo + Dy + "[Tt ]" + Hr + Mn + Sd + TZ + "$").exec(CurDate) ) {}; + + // If the date couldn't be parsed, return null + if ( !TF ) { return null }; + // Default the Time Fragments if they're not present + if ( !TF[2] ) { TF[2] = 1 } else { TF[2] = TF[2] - 1 }; + if ( !TF[3] ) { TF[3] = 1 }; + if ( !TF[4] ) { TF[4] = 0 }; + if ( !TF[5] ) { TF[5] = 0 }; + if ( !TF[6] ) { TF[6] = 0 }; + if ( !TF[7] ) { TF[7] = 0 }; + if ( !TF[8] ) { TF[8] = null }; + if ( TF[9] != "-" && TF[9] != "+" ) { TF[9] = null }; + if ( !TF[10] ) { TF[10] = 0 } else { TF[10] = TF[9] + TF[10] }; + if ( !TF[11] ) { TF[11] = 0 } else { TF[11] = TF[9] + TF[11] }; + // If there's no timezone info the data is local time + + TF[7] = 0; + + if ( !TF[8] && !TF[9] ) { + return new Date(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7]); + }; + // If the UTC indicator is set the date is UTC + if ( TF[8] == "Z" ) { + return new Date(Date.UTC(TF[1], TF[2], TF[3], TF[4], TF[5], TF[6], TF[7])); + }; + // If the date has a timezone offset + if ( TF[9] == "-" || TF[9] == "+" ) { + // Get current Timezone information + var CurTZ = new Date().getTimezoneOffset(); + var CurTZh = TF[10] - ((CurTZ >= 0 ? "-" : "+") + Math.floor(Math.abs(CurTZ) / 60)) + var CurTZm = TF[11] - ((CurTZ >= 0 ? "-" : "+") + (Math.abs(CurTZ) % 60)) + // Return the date + return new Date(TF[1], TF[2], TF[3], TF[4] - CurTZh, TF[5] - CurTZm, TF[6], TF[7]); + }; + // If we've reached here we couldn't deal with the input, return null + return null; + +}; + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* "Date" Object Prototype Extensions */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +Date.prototype.dateFormat = function(Mask) { + + var FormattedDate = ""; + var Ref_MonthFullName = ["January", "February", "March", "April", "May", + "June", "July", "August", "September", "October", "November", "December"]; + var Ref_MonthAbbreviation = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + var Ref_DayFullName = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + var Ref_DayAbbreviation = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + + // Convert any supported simple masks into "real" masks + switch (Mask) { + case "short": + Mask = "m/d/yy"; + break; + case "medium": + Mask = "mmm d, yyyy"; + break; + case "long": + Mask = "mmmm d, yyyy"; + break; + case "full": + Mask = "dddd, mmmm d, yyyy"; + break; + }; + + // Tack a temporary space at the end of the mask to ensure that the last character isn't a mask character + Mask += " "; + + // Parse the Mask + var CurChar; + var MaskPart = ""; + for ( var Cnt = 0; Cnt < Mask.length; Cnt++ ) { + // Get the character + CurChar = Mask.charAt(Cnt); + // Determine if the character is a mask element + if ( (CurChar != "d") && (CurChar != "m") && (CurChar != "y") ) { + // Determine if we need to parse a MaskPart or not + if ( MaskPart != "" ) { + // Convert the mask part to the date value + switch (MaskPart) { + case "d": + FormattedDate += this.getDate(); + break; + case "dd": + FormattedDate += ("0" + this.getDate()).slice(-2); + break; + case "ddd": + FormattedDate += Ref_DayAbbreviation[this.getDay()]; + break; + case "dddd": + FormattedDate += Ref_DayFullName[this.getDay()]; + break; + case "m": + FormattedDate += this.getMonth() + 1; + break; + case "mm": + FormattedDate += ("0" + (this.getMonth() + 1)).slice(-2); + break; + case "mmm": + FormattedDate += Ref_MonthAbbreviation[this.getMonth()]; + break; + case "mmmm": + FormattedDate += Ref_MonthFullName[this.getMonth()]; + break; + case "yy": + FormattedDate += ("0" + this.getFullYear()).slice(-2); + break; + case "yyyy": + FormattedDate += ("000" + this.getFullYear()).slice(-4); + break; + }; + // Reset the MaskPart to nothing + MaskPart = ""; + }; + // Add the character to the output + FormattedDate += CurChar; + } else { + // Add the current mask character to the MaskPart + MaskPart += CurChar; + }; + }; + + // Remove the temporary space from the end of the formatted date + FormattedDate = FormattedDate.substring(0,FormattedDate.length - 1); + + // Return the formatted date + return FormattedDate; + +}; + + +Date.prototype.timeFormat = function(Mask) { + + var FormattedTime = ""; + + // Convert any supported simple masks into "real" masks + switch (Mask) { + case "short": + Mask = "h:mm tt"; + break; + case "medium": + Mask = "h:mm:ss tt"; + break; + case "long": + Mask = "h:mm:ss.l tt"; + break; + case "full": + Mask = "h:mm:ss.l tt"; + break; + }; + + // Tack a temporary space at the end of the mask to ensure that the last character isn't a mask character + Mask += " "; + + // Parse the Mask + var CurChar; + var MaskPart = ""; + for ( var Cnt = 0; Cnt < Mask.length; Cnt++ ) { + // Get the character + CurChar = Mask.charAt(Cnt); + // Determine if the character is a mask element + if ( (CurChar != "h") && (CurChar != "H") && (CurChar != "m") && + (CurChar != "s") && (CurChar != "l") && (CurChar != "t") && (CurChar != "T") ) { + // Determine if we need to parse a MaskPart or not + if ( MaskPart != "" ) { + // Convert the mask part to the date value + switch (MaskPart) { + case "h": + var CurValue = this.getHours(); + if ( CurValue > 12 ) { + CurValue = CurValue - 12; + }; + FormattedTime += CurValue; + break; + case "hh": + var CurValue = this.getHours(); + if ( CurValue > 12 ) { + CurValue = CurValue - 12; + }; + FormattedTime += ("0" + CurValue).slice(-2); + break; + case "H": + FormattedTime += ("0" + this.getHours()).slice(-2); + break; + case "HH": + FormattedTime += ("0" + this.getHours()).slice(-2); + break; + case "m": + FormattedTime += this.getMinutes(); + break; + case "mm": + FormattedTime += ("0" + this.getMinutes()).slice(-2); + break; + case "s": + FormattedTime += this.getSeconds(); + break; + case "ss": + FormattedTime += ("0" + this.getSeconds()).slice(-2); + break; + case "l": + FormattedTime += ("00" + this.getMilliseconds()).slice(-3); + break; + case "t": + if ( this.getHours() > 12 ) { + FormattedTime += "p"; + } else { + FormattedTime += "a"; + }; + break; + case "tt": + if ( this.getHours() > 12 ) { + FormattedTime += "pm"; + } else { + FormattedTime += "am"; + }; + break; + case "T": + if ( this.getHours() > 12 ) { + FormattedTime += "P"; + } else { + FormattedTime += "A"; + }; + break; + case "TT": + if ( this.getHours() > 12 ) { + FormattedTime += "PM"; + } else { + FormattedTime += "AM"; + }; + break; + }; + // Reset the MaskPart to nothing + MaskPart = ""; + }; + // Add the character to the output + FormattedTime += CurChar; + } else { + // Add the current mask character to the MaskPart + MaskPart += CurChar; + }; + }; + + // Remove the temporary space from the end of the formatted date + FormattedTime = FormattedTime.substring(0,FormattedTime.length - 1); + + // Return the formatted date + return FormattedTime; + +}; + + +/* + dropTZ - do not include the timezone in the output. only used for YMDH+ + useSpace - if true, use a space instaed of a "T" between date and time +*/ +Date.prototype.iso8601Format = function(Style, isUTC, dropTZ, useSpace) { + + var FormattedDate = ""; + + switch (Style) { + case "Y": + FormattedDate += this.dateFormat("yyyy"); + break; + case "YM": + FormattedDate += this.dateFormat("yyyy-mm"); + break; + case "YMD": + FormattedDate += this.dateFormat("yyyy-mm-dd"); + break; + case "YMDHM": + FormattedDate += this.dateFormat("yyyy-mm-dd") + ((useSpace) ? " " : "T") + this.timeFormat("HH:mm"); + break; + case "YMDHMS": + FormattedDate += this.dateFormat("yyyy-mm-dd") + ((useSpace) ? " " : "T") + this.timeFormat("HH:mm:ss"); + break; + case "YMDHMSM": + FormattedDate += this.dateFormat("yyyy-mm-dd") + ((useSpace) ? " " : "T") + this.timeFormat("HH:mm:ss.l"); + break; + }; + + if ( !dropTZ && (Style == "YMDHM" || Style == "YMDHMS" || Style == "YMDHMSM") ) { + if ( isUTC ) { + FormattedDate += "Z"; + } else { + // Get TimeZone Information + var TimeZoneOffset = this.getTimezoneOffset(); + var TimeZoneInfo = (TimeZoneOffset >= 0 ? "-" : "+") + + ("0" + (Math.floor(Math.abs(TimeZoneOffset) / 60))).slice(-2) + ":" + + ("00" + (Math.abs(TimeZoneOffset) % 60)).slice(-2); + FormattedDate += TimeZoneInfo; + }; + }; + + // Return the date + return FormattedDate; + +}; + + + + diff --git a/Open-ILS/web/opac/common/js/RemoteRequest.js b/Open-ILS/web/opac/common/js/RemoteRequest.js index cd888761dc..53af228d9d 100644 --- a/Open-ILS/web/opac/common/js/RemoteRequest.js +++ b/Open-ILS/web/opac/common/js/RemoteRequest.js @@ -218,8 +218,6 @@ RemoteRequest.prototype.send = function(blocking) { if(this.cancelled) return; - - /* determine the xmlhttp server dynamically */ var url = location.protocol + "//" + location.host + "/" + XML_HTTP_GATEWAY; @@ -237,10 +235,10 @@ RemoteRequest.prototype.send = function(blocking) { var data = null; if( this.type == 'GET' ) url += "?" + this.param_string; - if(isXUL() && this.secure ) dump('SECURE = true\n'); - this.url = url; + //if( isXUL() ) dump('request URL = ' + url + '?' + this.param_string + '\n'); + try { if(blocking) this.xmlhttp.open(this.type, url, false); @@ -260,7 +258,7 @@ RemoteRequest.prototype.send = function(blocking) { try { var auth; try { auth = cookieManager.read(COOKIE_SES) } catch(ee) {} - if( !auth && isXUL() ) auth = ses(); + if( isXUL() ) auth = fetchXULStash().session.key; if( auth ) this.xmlhttp.setRequestHeader('X-OILS-Authtoken', auth); @@ -299,7 +297,7 @@ RemoteRequest.prototype.getResultObject = function() { var status = null; this.event(null); - /* DEBUG + /* try { dump(this.url + '?' + this.param_string + '\n' + 'Returned with \n\tstatus = ' + this.xmlhttp.status + @@ -322,6 +320,8 @@ RemoteRequest.prototype.getResultObject = function() { var text = this.xmlhttp.responseText; + //try{if(getDebug()) _debug('response: ' + text + '\n')}catch(e){} + if(text == "" || text == " " || text == null) { try { dump('dbg: Request returned no text!\n'); } catch(E) {} if(isXUL()) @@ -372,3 +372,19 @@ RemoteRequest.prototype.addParam = function(param) { this.param_string += "¶m=" + string; } +function fetchXULStash() { + if( isXUL() ) { + try { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + var __OILS = new Components.Constructor("@mozilla.org/openils_data_cache;1", "nsIOpenILS"); + var data_cache = new __OILS( ); + return data_cache.wrappedJSObject.OpenILS.prototype.data; + + } catch(E) { + _debug('Error in OpenILS.data._debug_stash(): ' + js2JSON(E) ); + } + } + return {}; +} + + diff --git a/Open-ILS/web/opac/common/js/config.js b/Open-ILS/web/opac/common/js/config.js index 7ef3dba48e..87c6ab639c 100644 --- a/Open-ILS/web/opac/common/js/config.js +++ b/Open-ILS/web/opac/common/js/config.js @@ -338,6 +338,7 @@ var FETCH_MR_DESCRIPTORS = 'open-ils.search:open-ils.search.metabib.record_to_d var FETCH_HIGHEST_PERM_ORG = 'open-ils.actor:open-ils.actor.user.perm.highest_org.batch'; var FETCH_USER_NOTES = 'open-ils.actor:open-ils.actor.note.retrieve.all'; var FETCH_ORG_BY_SHORTNAME = 'open-ils.actor:open-ils.actor.org_unit.retrieve_by_shorname'; +var FETCH_BIB_ID_BY_BARCODE = 'open-ils.search:open-ils.search.bib_id.by_barcode'; /* ---------------------------------------------------------------------------- */ diff --git a/Open-ILS/web/opac/common/js/opac_utils.js b/Open-ILS/web/opac/common/js/opac_utils.js index 47c9e0cb55..57babc144e 100644 --- a/Open-ILS/web/opac/common/js/opac_utils.js +++ b/Open-ILS/web/opac/common/js/opac_utils.js @@ -449,6 +449,11 @@ function buildSearchLink(type, string, linknode, trunc) { linknode.setAttribute("href", buildOPACLink(args)); } +function setSessionCookie(ses) { + cookieManager.write(COOKIE_SES, ses, -1); +} + + /* ----------------------------------------------------------------------- */ /* user session handling */ @@ -459,7 +464,11 @@ function buildSearchLink(type, string, linknode, trunc) { if ses != G.user.session, we also force a grab */ function grabUser(ses, force) { - if(!ses && isXUL()) ses = xulG['authtoken']; + if(!ses && isXUL()) { + stash = fetchXULStash(); + ses = stash.session.key + _debug("stash auth token = " + ses); + } if(!ses) { ses = cookieManager.read(COOKIE_SES); @@ -491,7 +500,7 @@ function grabUser(ses, force) { G.user = user; G.user.fleshed = false; G.user.session = ses; - cookieManager.write(COOKIE_SES, ses, -1); + setSessionCookie(ses); grabUserPrefs(); if(G.user.prefs['opac.hits_per_page']) @@ -541,7 +550,7 @@ function grabFleshedUser() { G.user.session = ses; G.user.fleshed = true; - cookieManager.write(COOKIE_SES, ses, '+1y'); /* update the cookie */ + setSessionCookie(ses); return G.user; } @@ -730,6 +739,7 @@ function buildOrgSelector(node) { for( var i in orgArraySearcher ) { var node = orgArraySearcher[i]; if( node == null ) continue; + if(!isXUL() && !isTrue(node.opac_visible())) continue; if(node.parent_ou() == null) tree.addNode(node.id(), -1, node.name(), "javascript:orgSelect(" + node.id() + ");", node.name()); @@ -756,12 +766,11 @@ function setFontSize(size) { cookieManager.write(COOKIE_FONT, size, '+1y'); } - var resourceFormats = [ "text", "moving image", "sound recording", "software, multimedia", - "still images", + "still image", "cartographic", "mixed material", "notated music", @@ -782,7 +791,7 @@ function modsFormatToMARC(format) { return "j"; case "software, multimedia": return "m"; - case "still images": + case "still image": return "k"; case "cartographic": return "ef"; @@ -811,7 +820,7 @@ function MARCFormatToMods(format) { case "m": return "software, multimedia"; case "k": - return "still images"; + return "still image"; case "e": case "f": return "cartographic"; diff --git a/Open-ILS/web/opac/common/js/org_utils.js b/Open-ILS/web/opac/common/js/org_utils.js index f86afca2f2..fd45f41684 100644 --- a/Open-ILS/web/opac/common/js/org_utils.js +++ b/Open-ILS/web/opac/common/js/org_utils.js @@ -85,6 +85,7 @@ for (var i in _l) { x.ou_type(_l[i][1]); x.parent_ou(_l[i][2]); x.name(_l[i][3]); + x.opac_visible(_l[i][4]); orgArraySearcher[x.id()] = x; } for (var i in orgArraySearcher) { diff --git a/Open-ILS/web/opac/common/js/utils.js b/Open-ILS/web/opac/common/js/utils.js index 27653aef4a..079ad73d08 100644 --- a/Open-ILS/web/opac/common/js/utils.js +++ b/Open-ILS/web/opac/common/js/utils.js @@ -359,6 +359,7 @@ function setSelectorVal( selector, index, name, value, action, indent ) { /* split on spaces. capitalize the first /\w/ character in each substring */ function normalize(val) { + return val; /* disable me for now */ if(!val) return ""; @@ -566,6 +567,15 @@ function alertId(id) { if(node) alert(node.innerHTML); } +function alertIdText(id, text) { + var node = $(id); + if(!node) return; + if(text) + alert(text + '\n\n' + node.innerHTML); + else + alert(node.innerHTML); +} + function confirmId(id) { var node = $(id); if(node) return confirm(node.innerHTML); diff --git a/Open-ILS/web/opac/extras/slimpac/start.html b/Open-ILS/web/opac/extras/slimpac/start.html index 5feaa9e63e..f6e1e24e49 100644 --- a/Open-ILS/web/opac/extras/slimpac/start.html +++ b/Open-ILS/web/opac/extras/slimpac/start.html @@ -7,7 +7,7 @@ border: 3px solid #A7EA9D; -moz-border-radius: 6px; padding: 6px; - margin-top: 65px;' + margin-top: 65px; } .greenrow { @@ -98,6 +98,12 @@
+ Dynamic Catalog + + + | + + Advanced Search diff --git a/Open-ILS/web/opac/skin/default/css/layout.css b/Open-ILS/web/opac/skin/default/css/layout.css index 93830dbb3e..eae2f6b54f 100644 --- a/Open-ILS/web/opac/skin/default/css/layout.css +++ b/Open-ILS/web/opac/skin/default/css/layout.css @@ -43,6 +43,8 @@ table { border-collapse: collapse; } .light_border { border: 1px solid #E0E0E0; } +/*.overdue { color: red; font-size: 110%; background: #F0F0E0; }*/ +.overdue { color: red; font-weight: bold;} /* #main_table { border-collapse: collapse; width: 100%; height: 100%; } diff --git a/Open-ILS/web/opac/skin/default/js/adv_global.js b/Open-ILS/web/opac/skin/default/js/adv_global.js index b5f2fdad49..f49d2e4a28 100644 --- a/Open-ILS/web/opac/skin/default/js/adv_global.js +++ b/Open-ILS/web/opac/skin/default/js/adv_global.js @@ -70,7 +70,7 @@ function advSelToStringList(sel) { for( var i = 0; i < list.length; i++ ) { var str = list[i]; for( var j = 0; j < str.length; j++ ) { - if(str.charAt(j) == ' ') continue; + //if(str.charAt(j) == ' ') continue; vals.push(str.charAt(j)); } } diff --git a/Open-ILS/web/opac/skin/default/js/advanced.js b/Open-ILS/web/opac/skin/default/js/advanced.js index 20f5a55aab..27e9994d9a 100644 --- a/Open-ILS/web/opac/skin/default/js/advanced.js +++ b/Open-ILS/web/opac/skin/default/js/advanced.js @@ -113,6 +113,10 @@ function advGenericSearch() { arg[PARAM_RTYPE] = RTYPE_TCN; break; + case 'barcode': + advFindBarcode(term); + break; + case 'cn': arg.page = CNBROWSE; @@ -142,3 +146,25 @@ function advGenericSearch() { } +function advFindBarcode(barcode) { + var req = new Request(FETCH_BIB_ID_BY_BARCODE, barcode); + req.callback(advDrawBarcode); + req.request.alertEvent = false; + req.send(); +} + +function advDrawBarcode(r) { + titleid = r.getResultObject(); + if(checkILSEvent(titleid)) { + alertId('myopac.copy.not.found'); + return; + } + if(!titleid) return; + var args = {}; + args.page = RDETAIL; + args[PARAM_RID] = titleid; + location.href = buildOPACLink(args); +} + + + diff --git a/Open-ILS/web/opac/skin/default/js/holds.js b/Open-ILS/web/opac/skin/default/js/holds.js index 9a0980b982..1bd6ca6a28 100644 --- a/Open-ILS/web/opac/skin/default/js/holds.js +++ b/Open-ILS/web/opac/skin/default/js/holds.js @@ -8,6 +8,8 @@ item_form and language are optional - if language exist and no item_form is specified, use item_type(s)--language */ +var noEmailMessage; + function holdsHandleStaff() { swapCanvas($('xulholds_box')); $('xul_recipient_barcode').focus(); @@ -52,6 +54,9 @@ function holdsDrawEditor(args) { holdArgs = (args) ? args : holdArgs; + if(!noEmailMessage) + noEmailMessage = $('holds_email').removeChild($('holds.no_email')); + if(isXUL() && holdArgs.recipient == null && holdArgs.editHold == null) { holdsHandleStaff(); @@ -402,6 +407,15 @@ function __holdsDrawWindow() { } } + if(!G.user.email()) { + $('holds_enable_email').checked = false; + $('holds_enable_email').disabled = true; + var n = noEmailMessage.cloneNode(true); + appendClear( $('holds_email'), n); + unHideMe(n); + $('holds.no_email.my_account').setAttribute('href', buildOPACLink({page:MYOPAC},null,true)); + } + if(!$('holds_phone').value) $('holds_enable_phone').checked = false; @@ -552,15 +566,22 @@ function holdsCheckPossibility(pickuplib, hold, recurse) { pickup_lib : pickuplib }; - _debug("hold possible args = "+js2JSON(args)); + if(recurse) { + /* if we're calling create again (recursing), + we know that the hold possibility check already succeeded */ + holdHandleCreateResponse({_recurse:true, _hold:hold}, true ); - var req = new Request(CHECK_HOLD_POSSIBLE, G.user.session, args ); - - req.request.alertEvent = false; - req.request._hold = hold; - req.request._recurse = recurse; - req.callback(holdHandleCreateResponse); - req.send(); + } else { + _debug("hold possible args = "+js2JSON(args)); + + var req = new Request(CHECK_HOLD_POSSIBLE, G.user.session, args ); + + req.request.alertEvent = false; + req.request._hold = hold; + req.request._recurse = recurse; + req.callback(holdHandleCreateResponse); + req.send(); + } } @@ -657,22 +678,25 @@ function holdsPlaceHold(hold, recurse) { } -function holdHandleCreateResponse(r) { - var res = r.getResultObject(); - if(!res || checkILSEvent(res) ) { - if(!res) { - alert($('hold_not_allowed').innerHTML); - } else { - if( res.textcode == 'PATRON_BARRED' ) { - alertId('hold_failed_patron_barred'); - } else { +function holdHandleCreateResponse(r, recurse) { + + if(!recurse) { + var res = r.getResultObject(); + if(!res || checkILSEvent(res) ) { + if(!res) { alert($('hold_not_allowed').innerHTML); + } else { + if( res.textcode == 'PATRON_BARRED' ) { + alertId('hold_failed_patron_barred'); + } else { + alert($('hold_not_allowed').innerHTML); + } } + swapCanvas($('holds_box')); + return; } - swapCanvas($('holds_box')); - return; - } - + } + holdCreateHold(r._recurse, r._hold); } @@ -688,14 +712,15 @@ function holdCreateHold( recurse, hold ) { showCanvas(); - holdArgs = null; runEvt('common', 'holdUpdated'); } function holdProcessResult( hold, res, recurse ) { + if( res == '1' ) { alert($('holds_success').innerHTML); + holdArgs = null; } else { diff --git a/Open-ILS/web/opac/skin/default/js/myopac.js b/Open-ILS/web/opac/skin/default/js/myopac.js index a8e6a901d0..bfe6a24ad2 100644 --- a/Open-ILS/web/opac/skin/default/js/myopac.js +++ b/Open-ILS/web/opac/skin/default/js/myopac.js @@ -118,6 +118,15 @@ var checkedRowTemplate; var circsCache = new Array(); var checkedDrawn = false; +function moClearCheckedTable() { + var tbody = $("myopac_checked_tbody"); + var loading = $("myopac_checked_loading"); + var none = $("myopac_checked_none"); + clearNodes( tbody, [ loading, none ] ); +} + +var __can_renew_one = false; + function myOPACDrawCheckedOutSlim(r) { var checked = r.getResultObject(); @@ -125,12 +134,14 @@ function myOPACDrawCheckedOutSlim(r) { var loading = $("myopac_checked_loading"); var none = $("myopac_checked_none"); + __can_renew_one = false; + if(checkedDrawn) return; checkedDrawn = true; if(!checkedRowTemplate) checkedRowTemplate = tbody.removeChild($("myopac_checked_row")); - clearNodes( tbody, [ loading, none ] ); + moClearCheckedTable(); hideMe(loading); /* remove all children and start over */ if(!(checked && (checked.out || checked.overdue))) { @@ -154,31 +165,55 @@ function myOPACDrawCheckedOutSlim(r) { req.send(); } + appendClear($('mo_items_out_count'), + text(new String( parseInt(checked.overdue.length) + parseInt(checked.out.length) )) ); + + if( checked.overdue.length > 0 ) { + addCSSClass($('mo_items_overdue_count'), 'overdue'); + appendClear($('mo_items_overdue_count'), + text(new String( parseInt(checked.overdue.length) )) ); + } + } + function myOPACDrawCheckedItem(r) { var circ = r.getResultObject(); var tbody = r.tbody; var row = checkedRowTemplate.cloneNode(true); row.id = 'myopac_checked_row_ ' + circ.id(); + row.setAttribute('circid', circ.id()); var due = _trimTime(circ.due_date()); var dlink = $n( row, "myopac_checked_due" ); var rlink = $n( row, "myopac_checked_renewals" ); - var rnlink = $n( row, "myopac_checked_renew_link" ); + //var rnlink = $n( row, "myopac_checked_renew_link" ); - if( r.od ) due = elem('b', {style:'color:red;font-size:110%'},due); - else due = text(due); + //if( r.od ) due = elem('b', {style:'color:red;font-size:110%'},due); + if( r.od ) { + due = elem('b', null, due); + addCSSClass(due, 'overdue'); + } else { + due = text(due); + } dlink.appendChild(due); rlink.appendChild(text(circ.renewal_remaining())); unHideMe(row); - rnlink.setAttribute('href', 'javascript:myOPACRenewCirc("'+circ.id()+'");'); + //rnlink.setAttribute('href', 'javascript:myOPACRenewCirc("'+circ.id()+'");'); circsCache.push(circ); - if( circ.renewal_remaining() < 1 ) hideMe(rnlink); + if( circ.renewal_remaining() < 1 ) { + $n(row, 'selectme').disabled = true; + if(!__can_renew_one) + $('mo_renew_button').disabled = true; + } else { + __can_renew_one = true; + $('mo_renew_button').disabled = false; + $n(row, 'selectme').disabled = false; + } tbody.appendChild(row); @@ -190,6 +225,8 @@ function myOPACDrawCheckedItem(r) { req.send(); } +var __circ_titles = {}; + function myOPACDrawCheckedTitle(r) { var record = r.getResultObject(); var circid = r.circ; @@ -207,6 +244,7 @@ function myOPACDrawCheckedTitle(r) { var alink = $n( row, "myopac_checked_author_link" ); buildTitleDetailLink(record, tlink); buildSearchLink(STYPE_AUTHOR, record.author(), alink); + __circ_titles[circid] = record.title(); } function myOPACDrawNonCatalogedItem(r) { @@ -219,9 +257,11 @@ function myOPACDrawNonCatalogedItem(r) { tlink.parentNode.appendChild(text(copy.dummy_title())); alink.parentNode.appendChild(text(copy.dummy_author())); + __circ_titles[circid] = copy.dummy_title(); } +/* function myOPACRenewCirc(circid) { var circ; @@ -232,7 +272,7 @@ function myOPACRenewCirc(circid) { if(!confirm($('myopac_renew_confirm').innerHTML)) return; var req = new Request(RENEW_CIRC, G.user.session, - { patron : G.user.id(), copyid : circ.target_copy() } ); + { patron : G.user.id(), copyid : circ.target_copy(), opac_renewal : 1 } ); req.request.alertEvent = false; req.send(true); var res = req.result(); @@ -246,6 +286,7 @@ function myOPACRenewCirc(circid) { checkedDrawn = false; myOPACShowChecked(); } +*/ @@ -464,10 +505,17 @@ function _finesFormatNumber(num) { //function _trimTime(time) { if(!time) return ""; return time.replace(/\ .*/,""); } function _trimTime(time) { if(!time) return ""; - return time.replace(/T.*/,""); + var d = Date.parseIso8601(time); + if(!d) return ""; /* date parse failed */ + return d.iso8601Format('YMD'); } -function _trimSeconds(time) { if(!time) return ""; return time.replace(/:\d\d\..*$/,""); } +function _trimSeconds(time) { + if(!time) return ""; + var d = Date.parseIso8601(time); + if(!d) return ""; /* date parse failed */ + return d.iso8601Format('YMDHM',null,true,true); +} function myOPACShowTransactions(r) { @@ -700,6 +748,12 @@ function _myOPACSummaryShowUer(r) { fleshedUser = user; if(!user) return; + var expireDate = Date.parseIso8601(user.expire_date()); + if( expireDate < new Date() ) { + appendClear($('myopac.expired.date'), expireDate.iso8601Format('YMD')); + unHideMe($('myopac.expired.alert')); + } + var iv1 = user.ident_value()+''; if (iv1.length > 4) iv1 = iv1.replace(new RegExp(iv1.substring(0,iv1.length - 4)), '***********'); @@ -1110,20 +1164,126 @@ function myOPACDrawNonCatCirc(r) { duration = parseInt(duration + '000'); var dtf = circ.circ_time(); + var start = Date.parseIso8601(circ.circ_time()); + var due = new Date( start.getTime() + duration ); + appendClear($n(row, 'circ_time'), text(due.iso8601Format('YMDHM', null, true, true))); +} - /*Date.W3CDTF is not happy with the milliseonds, nor is it - happy without minute component of the timezone */ - dtf = dtf.replace(/\.\d+/,''); - dtf += ":00"; - var start = new Date.W3CDTF(); - start.setW3CDTF(dtf); - var due = new Date( start.getTime() + duration ); - due = (due+'').replace(/(.*?:\d\d):.*/, '$1'); - appendClear($n(row, 'circ_time'), text(due)); + +function myopacSelectAllChecked() { + __myopacSelectChecked(true); +} + +function myopacSelectNoneChecked() { + __myopacSelectChecked(false); +} + +function __myopacSelectChecked(value) { + var rows = myopacGetCheckedOutRows(); + for( var i = 0; i < rows.length; i++ ) { + var row = rows[i]; + var box = $n(row, 'selectme'); + if( box && ! box.disabled ) + box.checked = value; + } +} + +function myopacGetCheckedOutRows() { + var rows = []; + var tbody = $('myopac_checked_tbody'); + var children = tbody.childNodes; + for( var i = 0; i < children.length; i++ ) { + var child = children[i]; + if( child.nodeName.match(/^tr$/i) ) + if( $n(child, 'selectme') ) + rows.push(child); + } + return rows; +} + +var __renew_circs = []; + +/* true if 1 renewal succeeded */ +var __success_count = 0; + +/* renews all selected circulations */ +function myOPACRenewSelected() { + var rows = myopacGetCheckedOutRows(); + if(!confirm($('myopac_renew_confirm').innerHTML)) return; + __success_count = 0; + + for( var i = 0; i < rows.length; i++ ) { + + var row = rows[i]; + if( ! $n(row, 'selectme').checked ) continue; + var circ_id = row.getAttribute('circid'); + + var circ; + for( var j = 0; j != circsCache.length; j++ ) + if(circsCache[j].id() == circ_id) + circ = circsCache[j]; + + __renew_circs.push(circ); + } + + if( __renew_circs.length == 0 ) return; + + unHideMe($('my_renewing')); + moClearCheckedTable(); + + for( var i = 0; i < __renew_circs.length; i++ ) { + var circ = __renew_circs[i]; + moRenewCirc( circ.target_copy(), G.user.id(), circ ); + } +} + + +/* renews a single circulation */ +function moRenewCirc(copy_id, user_id, circ) { + + _debug('renewing circ ' + circ.id() + ' with copy ' + copy_id); + var req = new Request(RENEW_CIRC, G.user.session, + { patron : user_id, + copyid : copy_id, + opac_renewal : 1 + } + ); + + req.request.alertEvent = false; + req.callback(myHandleRenewResponse); + req.request.circ = circ; + req.send(); } +/* handles the circ renew results */ +function myHandleRenewResponse(r) { + var res = r.getResultObject(); + var circ = r.circ; + + /* remove this circ from the list of circs to renew */ + __renew_circs = grep(__renew_circs, function(i) { return (i.id() != circ.id()); }); + + _debug("handling renew result for " + circ.id()); + + if(checkILSEvent(res) || checkILSEvent(res[0])) + alertIdText('myopac_renew_fail', __circ_titles[circ.id()]); + else __success_count++; + + if(__renew_circs) return; /* more to come */ + + __renew_circs = []; + + if( __success_count > 0 ) + alertIdText('myopac_renew_success', __success_count); + + hideMe($('my_renewing')); + checkedDrawn = false; + myOPACShowChecked(); +} + + diff --git a/Open-ILS/web/opac/skin/default/js/rdetail.js b/Open-ILS/web/opac/skin/default/js/rdetail.js index 2c5090451a..066d3fdbba 100644 --- a/Open-ILS/web/opac/skin/default/js/rdetail.js +++ b/Open-ILS/web/opac/skin/default/js/rdetail.js @@ -145,8 +145,6 @@ function rdetailViewMarc(r,id) { hideMe($('rdetail_extras_loading')); $('rdetail_view_marc_box').innerHTML = r.getResultObject(); - var d = new Date(); - var div = elem('div', { "class" : 'hide_me' }); var span = div.appendChild( elem('abbr') ); @@ -506,9 +504,12 @@ function _rdetailRows(node) { } } + /* don't show hidden orgs */ if(node) { + if(!isXUL() && !isTrue(node.opac_visible())) return; + var row = copyRow.cloneNode(true); row.id = "cp_info_" + node.id(); @@ -599,7 +600,8 @@ function _rdetailBuildInfoRows(r) { } } - if(isLocal) unHideMe(rowNode); + //if(isLocal) unHideMe(rowNode); + unHideMe(rowNode); rdetailSetPath( thisOrg, isLocal ); rdetailBuildBrowseInfo( rowNode, arr[1], isLocal, thisOrg ); diff --git a/Open-ILS/web/opac/skin/default/js/result_common.js b/Open-ILS/web/opac/skin/default/js/result_common.js index 9bce1feba4..4fa713e75e 100644 --- a/Open-ILS/web/opac/skin/default/js/result_common.js +++ b/Open-ILS/web/opac/skin/default/js/result_common.js @@ -10,7 +10,8 @@ if( findCurrentPage() == MRESULT || findCurrentPage() == RRESULT ) { G.evt.result.hitCountReceived.push(resultSetHitInfo); G.evt.result.recordReceived.push(resultDisplayRecord, resultAddCopyCounts); G.evt.result.copyCountsReceived.push(resultDisplayCopyCounts); - G.evt.result.allRecordsReceived.push(resultBuildCaches, resultDrawSubjects, resultDrawAuthors, resultDrawSeries); + G.evt.result.allRecordsReceived.push(resultBuildCaches, resultDrawSubjects, + resultDrawAuthors, resultDrawSeries, function(){unHideMe($('result_info_2'))}); attachEvt('result','lowHits',resultLowHits); attachEvt('result','zeroHits',resultZeroHits); attachEvt( "common", "locationUpdated", resultSBSubmit ); @@ -151,6 +152,9 @@ function resultSetHitInfo() { G.ui.result.current_page.appendChild(text(cpage)); G.ui.result.num_pages.appendChild(text(pages + ")")); /* the ) is dumb */ + $('current_page2').appendChild(text(cpage)); + $('num_pages2').appendChild(text(pages + ")")); /* the ) is dumb */ + /* set the offsets */ var offsetEnd = getDisplayCount() + getOffset(); if( getDisplayCount() > (getHitCount() - getOffset())) @@ -159,8 +163,14 @@ function resultSetHitInfo() { G.ui.result.offset_end.appendChild(text(offsetEnd)); G.ui.result.offset_start.appendChild(text(getOffset() + 1)); + $('offset_end2').appendChild(text(offsetEnd)); + $('offset_start2').appendChild(text(getOffset() + 1)); + G.ui.result.result_count.appendChild(text(getHitCount())); unHideMe(G.ui.result.info); + + $('result_count2').appendChild(text(getHitCount())); + unHideMe($('result_info_div2')); } function resultLowHits() { @@ -326,6 +336,9 @@ function resultPaginate() { G.ui.result.next_link.setAttribute("href", buildOPACLink(args)); addCSSClass(G.ui.result.next_link, config.css.result.nav_active); + $('next_link2').setAttribute("href", buildOPACLink(args)); + addCSSClass($('next_link2'), config.css.result.nav_active); + args[PARAM_OFFSET] = getHitCount() - (getHitCount() % getDisplayCount()); /* when hit count is divisible by display count, we have to adjust */ @@ -334,6 +347,9 @@ function resultPaginate() { G.ui.result.end_link.setAttribute("href", buildOPACLink(args)); addCSSClass(G.ui.result.end_link, config.css.result.nav_active); + + $('end_link2').setAttribute("href", buildOPACLink(args)); + addCSSClass($('end_link2'), config.css.result.nav_active); } if( o > 0 ) { @@ -347,13 +363,21 @@ function resultPaginate() { G.ui.result.prev_link.setAttribute( "href", buildOPACLink(args)); addCSSClass(G.ui.result.prev_link, config.css.result.nav_active); + $('prev_link2').setAttribute( "href", buildOPACLink(args)); + addCSSClass($('prev_link2'), config.css.result.nav_active); + args[PARAM_OFFSET] = 0; G.ui.result.home_link.setAttribute( "href", buildOPACLink(args)); addCSSClass(G.ui.result.home_link, config.css.result.nav_active); + + $('search_home_link2').setAttribute( "href", buildOPACLink(args)); + addCSSClass($('search_home_link2'), config.css.result.nav_active); } - if(getDisplayCount() < getHitCount()) + if(getDisplayCount() < getHitCount()) { unHideMe($('start_end_links_span')); + unHideMe($('start_end_links_span2')); + } showCanvas(); try{searchTimer.stop()}catch(e){} diff --git a/Open-ILS/web/opac/skin/default/js/rresult.js b/Open-ILS/web/opac/skin/default/js/rresult.js index 7fd0532cc0..c314dfd33b 100644 --- a/Open-ILS/web/opac/skin/default/js/rresult.js +++ b/Open-ILS/web/opac/skin/default/js/rresult.js @@ -229,6 +229,8 @@ var rresultTries = 0; function rresultHandleRIds(r) { var res = r.getResultObject(); + if(!res) res = {count:0,ids:[]}; + if( res.count == 0 && rresultTries == 0 && ! r.noretry) { rresultTries++; diff --git a/Open-ILS/web/opac/skin/default/xml/advanced/advanced_global.xml b/Open-ILS/web/opac/skin/default/xml/advanced/advanced_global.xml index 4b7490aeaa..6232cb5505 100644 --- a/Open-ILS/web/opac/skin/default/xml/advanced/advanced_global.xml +++ b/Open-ILS/web/opac/skin/default/xml/advanced/advanced_global.xml @@ -185,7 +185,7 @@ @@ -228,13 +228,18 @@ + + - @@ -242,6 +247,7 @@ + @@ -257,6 +263,9 @@ + + + diff --git a/Open-ILS/web/opac/skin/default/xml/common/css_common.xml b/Open-ILS/web/opac/skin/default/xml/common/css_common.xml index fea5f3170d..e7fc06cd63 100644 --- a/Open-ILS/web/opac/skin/default/xml/common/css_common.xml +++ b/Open-ILS/web/opac/skin/default/xml/common/css_common.xml @@ -15,5 +15,5 @@ + href="http:///opac/extras/opensearch/1.1/-/osd.xml"> diff --git a/Open-ILS/web/opac/skin/default/xml/common/holds.xml b/Open-ILS/web/opac/skin/default/xml/common/holds.xml index 5c6170c1ba..b605871528 100644 --- a/Open-ILS/web/opac/skin/default/xml/common/holds.xml +++ b/Open-ILS/web/opac/skin/default/xml/common/holds.xml @@ -90,7 +90,11 @@ &opac.holds.concactEmail;: - + + + (See My Account for setting your email address) + + Enable email notifications for this hold? @@ -165,7 +169,8 @@ No items were found that could fulfill the requested holds. It's possible that choosing a different format will result in a successful hold. - Otherwise, please consult your local librarian. + It is also possible that you have exceeded the number of allowable holds. + For further information, please consult your local librarian.
diff --git a/Open-ILS/web/opac/skin/default/xml/common/sidebar.xml b/Open-ILS/web/opac/skin/default/xml/common/sidebar.xml index 0b54c218f3..fc9789c5c5 100644 --- a/Open-ILS/web/opac/skin/default/xml/common/sidebar.xml +++ b/Open-ILS/web/opac/skin/default/xml/common/sidebar.xml @@ -114,6 +114,7 @@ + @@ -121,6 +122,7 @@ + No copy with the requested barcode was found diff --git a/Open-ILS/web/opac/skin/default/xml/footer.xml b/Open-ILS/web/opac/skin/default/xml/footer.xml index 14e58562a2..d79853320a 100644 --- a/Open-ILS/web/opac/skin/default/xml/footer.xml +++ b/Open-ILS/web/opac/skin/default/xml/footer.xml @@ -14,6 +14,11 @@ style='width: 60%; text-align:center; padding: 10px; font-size: 8pt;'> + Basic Catalog (HTML only) + + | + Find a Library Near Me diff --git a/Open-ILS/web/opac/skin/default/xml/home/index_body.xml b/Open-ILS/web/opac/skin/default/xml/home/index_body.xml index ed16e34c88..f32f10641f 100644 --- a/Open-ILS/web/opac/skin/default/xml/home/index_body.xml +++ b/Open-ILS/web/opac/skin/default/xml/home/index_body.xml @@ -1,5 +1,18 @@ - + + + +
@@ -14,6 +27,15 @@
+
diff --git a/Open-ILS/web/opac/skin/default/xml/myopac/myopac_checked.xml b/Open-ILS/web/opac/skin/default/xml/myopac/myopac_checked.xml index 24ad3866f3..a2078b2555 100644 --- a/Open-ILS/web/opac/skin/default/xml/myopac/myopac_checked.xml +++ b/Open-ILS/web/opac/skin/default/xml/myopac/myopac_checked.xml @@ -1,25 +1,50 @@
- - +
diff --git a/Open-ILS/web/opac/skin/default/xml/myopac/myopac_summary.xml b/Open-ILS/web/opac/skin/default/xml/myopac/myopac_summary.xml index a1ffb88e47..56086e6552 100644 --- a/Open-ILS/web/opac/skin/default/xml/myopac/myopac_summary.xml +++ b/Open-ILS/web/opac/skin/default/xml/myopac/myopac_summary.xml @@ -1,6 +1,20 @@
+ +
+
+ + + + + +
+ Your account expired on ! + Please see a librarian to renew your account. +
+
+
@@ -19,26 +33,8 @@
-
- -
Name diff --git a/Open-ILS/web/opac/skin/default/xml/page_myopac.xml b/Open-ILS/web/opac/skin/default/xml/page_myopac.xml index 8524352bad..6c93feb646 100644 --- a/Open-ILS/web/opac/skin/default/xml/page_myopac.xml +++ b/Open-ILS/web/opac/skin/default/xml/page_myopac.xml @@ -1,6 +1,8 @@
+
Loading...
diff --git a/Open-ILS/web/opac/skin/default/xml/result/result_table.xml b/Open-ILS/web/opac/skin/default/xml/result/result_table.xml index 83515fe7ae..5a8ff081ef 100644 --- a/Open-ILS/web/opac/skin/default/xml/result/result_table.xml +++ b/Open-ILS/web/opac/skin/default/xml/result/result_table.xml @@ -108,7 +108,7 @@ - + @@ -136,6 +136,56 @@
+ + + + + + + + + + + + +
+ + + Results + + - + + &common.ofAtLeast; + + (page + + &common.of; + + + + + Start<< + + + >>End + + + + +
+ + + ' + window.escape(msg) + '','receipt_temp','chrome,resizable'); w.minimize(); w.go_print = function() { - - //setTimeout( - // function() { - try { - obj.NSPrint(w, silent, params); - } catch(E) { - obj.error.standard_unexpected_error_alert("Print Error in util.print.simple. After this dialog we'll try a second print attempt. content_type = " + content_type,E); - w.print(); - } - w.minimize(); w.close(); - // }, 0 - //); - + try { + obj.NSPrint(w, silent, params); + } catch(E) { + obj.error.standard_unexpected_error_alert("Print Error in util.print.simple. After this dialog we'll try a second print attempt. content_type = " + content_type,E); + w.print(); + } + w.minimize(); w.close(); } break; default: @@ -101,100 +119,17 @@ util.print.prototype = { dump(E+'\n'); } var cols = []; - // FIXME -- This could be done better.. instead of finding the columns and handling a tree dump, - // we could do a dump_with_keys instead - /* - switch(params.type) { - case 'offline_checkout' : - JSAN.use('circ.util'); - cols = util.functional.map_list( - circ.util.offline_checkout_columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - - break; - case 'offline_checkin' : - JSAN.use('circ.util'); - cols = util.functional.map_list( - circ.util.offline_checkin_columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - - break; - case 'offline_renew' : - JSAN.use('circ.util'); - cols = util.functional.map_list( - circ.util.offline_renew_columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - break; - case 'offline_inhouse_use' : - JSAN.use('circ.util'); - cols = util.functional.map_list( - circ.util.offline_inhouse_use_columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - break; - case 'items': - JSAN.use('circ.util'); - cols = util.functional.map_list( - circ.util.columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - break; - case 'bills': - JSAN.use('patron.util'); - cols = util.functional.map_list( - patron.util.mbts_columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - break; - case 'payment': - //cols = [ '%bill_id%','%payment%']; - cols = []; - break; - case 'transits': - cols = []; - break; - case 'holds': - JSAN.use('circ.util'); - cols = util.functional.map_list( - circ.util.hold_columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - break; - case 'patrons': - JSAN.use('patron.util'); - cols = util.functional.map_list( - patron.util.columns( {} ), - function(o) { - return '%' + o.id + '%'; - } - ); - break; - } - */ - var s = this.template_sub( params.header, cols, params ); - for (var i = 0; i < params.list.length; i++) { - params.row = params.list[i]; - s += this.template_sub( params.line_item, cols, params ); + var s = ''; + if (params.header) s += this.template_sub( params.header, cols, params ); + if (params.list) { + for (var i = 0; i < params.list.length; i++) { + params.row = params.list[i]; + params.row_idx = i; + s += this.template_sub( params.line_item, cols, params ); + } } - s += this.template_sub( params.footer, cols, params ); + if (params.footer) s += this.template_sub( params.footer, cols, params ); if (params.sample_frame) { var jsrc = 'data:text/javascript,' + window.escape('var params = { "data" : ' + js2JSON(params.data) + ', "list" : ' + js2JSON(params.list) + '};'); @@ -209,6 +144,9 @@ util.print.prototype = { JSAN.use('util.date'); var s = msg; var b; + try{b = s; s = s.replace(/%LINE_NO%/,Number(params.row_idx)+1);} + catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} + try{b = s; s = s.replace(/%patron_barcode%/,params.patron_barcode);} catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} @@ -216,12 +154,16 @@ util.print.prototype = { catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} try{b = s; s = s.replace(/%PINES_CODE%/,params.lib.shortname());} catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} + try{b = s; s = s.replace(/%SHORTNAME%/,params.lib.shortname());} + catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} try{b = s; s = s.replace(/%STAFF_FIRSTNAME%/,params.staff.first_given_name());} catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} try{b = s; s = s.replace(/%STAFF_LASTNAME%/,params.staff.family_name());} catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} try{b = s; s = s.replace(/%STAFF_BARCODE%/,params.staff.barcode); } catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} + try{b = s; s = s.replace(/%STAFF_PROFILE%/,obj.data.hash.pgt[ params.staff.profile() ].name() ); } + catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} try{b = s; s = s.replace(/%PATRON_FIRSTNAME%/,params.patron.first_given_name());} catch(E){s = b; this.error.sdump('D_WARN','string = <' + s + '> error = ' + js2JSON(E)+'\n');} try{b = s; s = s.replace(/%PATRON_LASTNAME%/,params.patron.family_name());} @@ -253,7 +195,7 @@ util.print.prototype = { try { if (typeof params.row != 'undefined') { if (params.row.length >= 0) { - alert('debug pause'); + alert('debug - please tell the developers that deprecated template code tried to execute'); for (var i = 0; i < cols.length; i++) { var re = new RegExp(cols[i],"g"); try{b = s; s=s.replace(re, params.row[i]);} @@ -286,11 +228,14 @@ util.print.prototype = { if (!w) w = window; var obj = this; try { + if (!params) params = {}; + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + obj.data.init({'via':'stash'}); - if (obj.data.print_strategy) { + if (params.print_strategy || obj.data.print_strategy) { - switch(obj.data.print_strategy) { + switch(params.print_strategy || obj.data.print_strategy) { case 'dos.print': obj._NSPrint_dos_print(w,silent,params); break; @@ -312,7 +257,7 @@ util.print.prototype = { } } catch (e) { - //alert('Probably not printing: ' + e); + alert('Probably not printing: ' + e); this.error.sdump('D_ERROR','PRINT EXCEPTION: ' + js2JSON(e) + '\n'); } @@ -321,15 +266,22 @@ util.print.prototype = { '_NSPrint_dos_print' : function(w,silent,params) { var obj = this; try { - /* This is a kludge/workaround. webBrowserPrint doesn't always work. So we're going to let + + /* OLD way: This is a kludge/workaround. webBrowserPrint doesn't always work. So we're going to let the html window handle our receipt template rendering, and then force a selection of all the text nodes and dump that to a file, for printing through a dos utility */ + /* netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); w.getSelection().selectAllChildren(w.document.firstChild); var text = w.getSelection().toString(); + */ + + /* NEW way: we just pass in the text */ - JSAN.use('util.file'); var file = new util.file('receipt.txt'); + var text = w; + + var file = new util.file('receipt.txt'); file.write_content('truncate',text); file.close(); file = new util.file('receipt.bat'); @@ -348,7 +300,7 @@ util.print.prototype = { } catch (e) { //alert('Probably not printing: ' + e); - this.error.sdump('D_ERROR','PRINT EXCEPTION: ' + js2JSON(e) + '\n'); + this.error.sdump('D_ERROR','_NSPrint_dos_print PRINT EXCEPTION: ' + js2JSON(e) + '\n'); } }, @@ -377,7 +329,7 @@ util.print.prototype = { // Pressing cancel is expressed as an NS_ERROR_ABORT return value, // causing an exception to be thrown which we catch here. // Unfortunately this will also consume helpful failures - this.error.sdump('D_ERROR','PRINT EXCEPTION: ' + js2JSON(e) + '\n'); + this.error.sdump('D_ERROR','_NSPrint_webBrowserPrint PRINT EXCEPTION: ' + js2JSON(e) + '\n'); } }, @@ -449,7 +401,7 @@ util.print.prototype = { 'load_settings' : function() { try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); - JSAN.use('util.file'); var file = new util.file('gPrintSettings'); + var file = new util.file('gPrintSettings'); if (file._file.exists()) { temp = file.get_object(); file.close(); for (var i in temp) { @@ -474,9 +426,12 @@ util.print.prototype = { 'save_settings' : function() { try { + var obj = this; netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); - JSAN.use('util.file'); var file = new util.file('gPrintSettings'); - file.set_object(this.gPrintSettings); file.close(); + var file = new util.file('gPrintSettings'); + if (typeof obj.gPrintSettings == 'undefined') obj.GetPrintSettings(); + if (obj.gPrintSettings) file.set_object(obj.gPrintSettings); + file.close(); } catch(E) { this.error.standard_unexpected_error_alert("save_settings()",E); } diff --git a/Open-ILS/xul/staff_client/chrome/content/util/rbrowser.xul b/Open-ILS/xul/staff_client/chrome/content/util/rbrowser.xul index 16ddb0fc25..4dd9aa23ac 100644 --- a/Open-ILS/xul/staff_client/chrome/content/util/rbrowser.xul +++ b/Open-ILS/xul/staff_client/chrome/content/util/rbrowser.xul @@ -31,6 +31,7 @@ @@ -178,13 +213,15 @@ - TCN: + TCN: () Created By: + Last Edited By: + Last Edited On: diff --git a/Open-ILS/xul/staff_client/server/cat/copy_browser.js b/Open-ILS/xul/staff_client/server/cat/copy_browser.js index f2bd5c4a9c..8c31a51937 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_browser.js +++ b/Open-ILS/xul/staff_client/server/cat/copy_browser.js @@ -874,7 +874,7 @@ cat.copy_browser.prototype = { 'command', function(ev) { //obj.show_my_libs(ev.target.value); - JSAN.use('util.file'); var file = new util.file('copy_browser_prefs'); + JSAN.use('util.file'); var file = new util.file('copy_browser_prefs.'+obj.data.server_unadorned); util.widgets.save_attributes(file, { 'lib_menu' : [ 'value' ], 'show_acns' : [ 'checked' ], 'show_acps' : [ 'checked' ] }); obj.refresh_list(); }, @@ -886,14 +886,18 @@ cat.copy_browser.prototype = { JSAN.use('util.widgets'); - file = new util.file('copy_browser_prefs'); + file = new util.file('copy_browser_prefs.'+obj.data.server_unadorned); util.widgets.load_attributes(file); ml.value = ml.getAttribute('value'); + if (! ml.value) { + ml.value = org.id(); + ml.setAttribute('value',ml.value); + } document.getElementById('show_acns').addEventListener( 'command', function(ev) { - JSAN.use('util.file'); var file = new util.file('copy_browser_prefs'); + JSAN.use('util.file'); var file = new util.file('copy_browser_prefs.'+obj.data.server_unadorned); util.widgets.save_attributes(file, { 'lib_menu' : [ 'value' ], 'show_acns' : [ 'checked' ], 'show_acps' : [ 'checked' ] }); }, false @@ -902,7 +906,7 @@ cat.copy_browser.prototype = { document.getElementById('show_acps').addEventListener( 'command', function(ev) { - JSAN.use('util.file'); var file = new util.file('copy_browser_prefs'); + JSAN.use('util.file'); var file = new util.file('copy_browser_prefs.'+obj.data.server_unadorned); util.widgets.save_attributes(file, { 'lib_menu' : [ 'value' ], 'show_acns' : [ 'checked' ], 'show_acps' : [ 'checked' ] }); }, false @@ -1207,6 +1211,8 @@ cat.copy_browser.prototype = { }, 'skip_all_columns_except' : [0,1,2], 'retrieve_id' : 'aou_' + org.id(), + 'to_bottom' : true, + 'no_auto_select' : true, }; var acn_tree_list; @@ -1302,6 +1308,8 @@ cat.copy_browser.prototype = { 'skip_all_columns_except' : [0,1,2], 'retrieve_id' : 'acn_' + acn_tree.id(), 'node' : parent_node, + 'to_bottom' : true, + 'no_auto_select' : true, }; var node = obj.list.append(data); obj.map_tree[ 'acn_' + acn_tree.id() ] = node; @@ -1351,6 +1359,8 @@ cat.copy_browser.prototype = { }, 'retrieve_id' : 'acp_' + acp_item.id(), 'node' : parent_node, + 'to_bottom' : true, + 'no_auto_select' : true, }; var node = obj.list.append(data); obj.map_tree[ 'acp_' + acp_item.id() ] = node; @@ -1429,6 +1439,7 @@ cat.copy_browser.prototype = { JSAN.use('util.list'); obj.list = new util.list('copy_tree'); obj.list.init( { + 'no_auto_select' : true, 'columns' : columns, 'map_row_to_columns' : circ.util.std_map_row_to_columns(' '), 'retrieve_row' : function(params) { diff --git a/Open-ILS/xul/staff_client/server/cat/copy_buckets.js b/Open-ILS/xul/staff_client/server/cat/copy_buckets.js index feab5e8e6f..21c716945e 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_buckets.js +++ b/Open-ILS/xul/staff_client/server/cat/copy_buckets.js @@ -19,7 +19,7 @@ cat.copy_buckets.prototype = { if (this.first_pause) { this.first_pause = false; } else { - alert("Pausing for replicated databases... press Enter or Spacebar when ready."); + alert("Action completed."); } var obj = this; obj.list1.clear(); @@ -58,6 +58,7 @@ cat.copy_buckets.prototype = { try { JSAN.use('util.functional'); var sel = obj.list1.retrieve_selection(); + document.getElementById('clip_button1').disabled = sel.length < 1; obj.selection_list1 = util.functional.map_list( sel, function(o) { return JSON2js(o.getAttribute('retrieve_id')); } @@ -87,7 +88,7 @@ cat.copy_buckets.prototype = { try { JSAN.use('util.functional'); var sel = obj.list2.retrieve_selection(); - document.getElementById('clip_button').disabled = sel.length < 1; + document.getElementById('clip_button2').disabled = sel.length < 1; obj.selection_list2 = util.functional.map_list( sel, function(o) { return JSON2js(o.getAttribute('retrieve_id')); } @@ -115,10 +116,22 @@ cat.copy_buckets.prototype = { obj.controller.init( { 'control_map' : { - 'sel_clip' : [ + 'save_columns2' : [ + ['command'], + function() { obj.list2.save_columns(); } + ], + 'save_columns1' : [ + ['command'], + function() { obj.list1.save_columns(); } + ], + 'sel_clip2' : [ ['command'], function() { obj.list2.clipboard(); } ], + 'sel_clip1' : [ + ['command'], + function() { obj.list1.clipboard(); } + ], 'copy_buckets_menulist_placeholder' : [ ['render'], function(e) { @@ -290,7 +303,7 @@ cat.copy_buckets.prototype = { obj.error.standard_unexpected_error_alert('Deletion likely failed.',E); } } - alert("Pausing for replicated databases... press Enter or Spacebar when ready."); + alert("Action completed."); setTimeout( function() { JSAN.use('util.widgets'); @@ -310,7 +323,7 @@ cat.copy_buckets.prototype = { obj.list2.clear(); var robj = obj.network.simple_request('BUCKET_DELETE',[ses(),'copy',bucket]); if (typeof robj == 'object') throw robj; - alert("Pausing for replicated databases... press Enter or Spacebar when ready."); + alert("Action completed."); obj.controller.render('copy_buckets_menulist_placeholder'); } catch(E) { obj.error.standard_unexpected_error_alert('Bucket deletion likely failed.',E); @@ -547,6 +560,68 @@ cat.copy_buckets.prototype = { obj.list2.full_retrieve(); } ], + 'cmd_export1' : [ + ['command'], + function() { + obj.list1.on_all_fleshed = function() { + try { + dump(obj.list1.dump_csv() + '\n'); + copy_to_clipboard(obj.list1.dump_csv()); + setTimeout(function(){obj.list1.on_all_fleshed = null;},0); + } catch(E) { + alert(E); + } + } + obj.list1.full_retrieve(); + } + ], + + 'cmd_print_export1' : [ + ['command'], + function() { + try { + obj.list1.on_all_fleshed = + function() { + try { + dump( obj.list1.dump_csv() + '\n' ); + //copy_to_clipboard(obj.list.dump_csv()); + JSAN.use('util.print'); var print = new util.print(); + print.simple(obj.list1.dump_csv(),{'content_type':'text/plain'}); + setTimeout(function(){ obj.list1.on_all_fleshed = null; },0); + } catch(E) { + obj.error.standard_unexpected_error_alert('print export',E); + } + } + obj.list1.full_retrieve(); + } catch(E) { + obj.error.standard_unexpected_error_alert('print export',E); + } + } + ], + + + 'cmd_print_export2' : [ + ['command'], + function() { + try { + obj.list2.on_all_fleshed = + function() { + try { + dump( obj.list2.dump_csv() + '\n' ); + //copy_to_clipboard(obj.list.dump_csv()); + JSAN.use('util.print'); var print = new util.print(); + print.simple(obj.list2.dump_csv(),{'content_type':'text/plain'}); + setTimeout(function(){ obj.list2.on_all_fleshed = null; },0); + } catch(E) { + obj.error.standard_unexpected_error_alert('print export',E); + } + } + obj.list2.full_retrieve(); + } catch(E) { + obj.error.standard_unexpected_error_alert('print export',E); + } + } + ], 'cmd_copy_buckets_reprint' : [ ['command'], @@ -569,7 +644,10 @@ cat.copy_buckets.prototype = { obj.list2.dump_retrieve_ids(), function(o) { return JSON2js(o)[1]; } ); - var url = urls.XUL_COPY_STATUS + '?barcodes=' + window.escape( js2JSON(barcodes) ); + var url = urls.XUL_COPY_STATUS; // + '?barcodes=' + window.escape( js2JSON(barcodes) ); + JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve(); + data.temp_barcodes_for_copy_status = barcodes; + data.stash('temp_barcodes_for_copy_status'); xulG.new_tab( url, {}, {}); } catch(E) { obj.error.standard_unexpected_error_alert('Copy Status from Copy Buckets',E); diff --git a/Open-ILS/xul/staff_client/server/cat/copy_buckets.xul b/Open-ILS/xul/staff_client/server/cat/copy_buckets.xul index 8dc6fb9c5e..d4097b36e3 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_buckets.xul +++ b/Open-ILS/xul/staff_client/server/cat/copy_buckets.xul @@ -76,7 +76,13 @@ - + + + + + + + diff --git a/Open-ILS/xul/staff_client/server/cat/copy_buckets_overlay.xul b/Open-ILS/xul/staff_client/server/cat/copy_buckets_overlay.xul index dc8b9fb9c0..5240c74fb9 100644 --- a/Open-ILS/xul/staff_client/server/cat/copy_buckets_overlay.xul +++ b/Open-ILS/xul/staff_client/server/cat/copy_buckets_overlay.xul @@ -31,6 +31,10 @@ +