#!/usr/bin/perl -w
use strict;
-use lib '../src/perlmods/';
-use lib '../../OpenSRF/src/perlmods/';
-use lib '../src/perlmods/OpenILS/Utils/';
+use lib '../src/perlmods/lib/';
+use lib '../src/perlmods/lib/OpenILS/Utils/';
use OpenSRF::Utils::JSON;
use OpenSRF::System;
if BUILDILSCORE
#Add directories to build
-OILSCORE_DIRS = c-apps extras
+OILSCORE_DIRS = c-apps extras perlmods
#Add manual (non-automake) install targets for simplicity of installing.
OILSCORE_INST = ilscore-install
install-data-hook: $(OILSCORE_INST) $(OILSWEB_INST) $(OILSUPDATES_INST) $(OILSREP_INST)
uninstall-hook:
- rm -R $(perldir)
rm -R $(TEMPLATEDIR)
rm -R $(XSLDIR)
rm -R $(CGIDIR)
#perl-install and string-templates-install
ilscore-install:
@echo $@
- @echo "Installing Perl modules"
- $(MKDIR_P) $(perldir)
$(MKDIR_P) $(TEMPLATEDIR)
- cp -r @srcdir@/perlmods/* $(perldir)
cp -r @srcdir@/templates/marc $(TEMPLATEDIR)
cp -r @srcdir@/templates/password-reset $(TEMPLATEDIR)
- sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@libdir@/perl5/OpenILS/WWW/Web.pm'
- sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@libdir@/perl5/OpenILS/WWW/Method.pm'
@echo "Installing string templates to $(TEMPLATEDIR)"
$(MKDIR_P) $(TEMPLATEDIR)
$(MKDIR_P) $(datadir)/overdue/
--- /dev/null
+#!/usr/bin/perl
+
+use Module::Build;
+use strict;
+use warnings;
+
+my $build = Module::Build->new(
+ module_name => 'OpenILS',
+ license => 'gpl',
+ requires => {
+ 'Apache2::Const' => '0',
+ 'Apache2::Log' => '0',
+ 'Apache2::RequestIO' => '0',
+ 'Apache2::RequestRec' => '0',
+ 'Apache2::RequestUtil' => '0',
+ 'APR::Const' => '0',
+ 'APR::Table' => '0',
+ 'Business::CreditCard' => '0',
+ 'Business::EDI' => '0',
+ 'Business::ISBN' => '0',
+ 'Business::OnlinePayment' => '0',
+ 'Carp' => '0',
+ 'CGI' => '0',
+ 'Class::DBI' => '0',
+ 'Class::DBI::AbstractSearch' => '0',
+ 'Data::Dumper' => '0',
+ 'DateTime' => '0',
+ 'DateTime::Format::ISO8601' => '0',
+ 'DateTime::Format::Mail' => '0',
+ 'DateTime::Format::Strptime' => '0',
+ 'DateTime::Set' => '0',
+ 'DateTime::SpanSet' => '0',
+ 'DBI' => '0',
+ 'Digest::MD5' => '0',
+ 'Email::Send' => '0',
+ 'Encode' => '0',
+ 'Error' => '0',
+ 'Exporter' => '0',
+ 'File::Basename' => '0',
+ 'File::Spec' => '0',
+ 'File::stat' => '0',
+ 'File::Temp' => '0',
+ 'Getopt::Long' => '0',
+ 'IO::Scalar' => '0',
+ 'JavaScript::SpiderMonkey' => '0',
+ 'List::Util' => '0',
+ 'Locale::Country' => '0',
+ 'LWP::UserAgent' => '0',
+ 'MARC::Batch' => '0',
+ 'MARC::Field' => '0',
+ 'MARC::File::XML' => '0',
+ 'MARC::Record' => '0',
+ 'MIME::Base64' => '0',
+ 'Net::FTP' => '0',
+ 'Net::SSH2' => '0',
+ 'OpenSRF::Application' => '0',
+ 'OpenSRF::AppSession' => '0',
+ 'OpenSRF::EX' => '0',
+ 'OpenSRF::MultiSession' => '0',
+ 'OpenSRF::System' => '0',
+ 'OpenSRF::Utils' => '0',
+ 'OpenSRF::Utils::Cache' => '0',
+ 'OpenSRF::Utils::Config' => '0',
+ 'OpenSRF::Utils::JSON' => '0',
+ 'OpenSRF::Utils::Logger' => '0',
+ 'OpenSRF::Utils::SettingsClient' => '0',
+ 'OpenSRF::Utils::SettingsParser' => '0',
+ 'Parse::RecDescent' => '0',
+ 'POSIX' => '0',
+ 'RPC::XML' => '0',
+ 'RPC::XML::Client' => '0',
+ 'RPC::XML::Function' => '0',
+ 'RPC::XML::Method' => '0',
+ 'RPC::XML::Parser' => '0',
+ 'RPC::XML::Procedure' => '0',
+ 'Safe' => '0',
+ 'Scalar::Util' => '0',
+ 'Socket' => '0',
+ 'SRU::Request' => '0',
+ 'SRU::Response' => '0',
+ 'Sys::Syslog' => '0',
+ 'Template' => '0',
+ 'Template::Plugin' => '0',
+ 'Test::More' => '0',
+ 'Text::Aspell' => '0',
+ 'Text::CSV' => '0',
+ 'Text::Glob' => '0',
+ 'Time::HiRes' => '0',
+ 'Time::Local' => '0',
+ 'Unicode::Normalize' => '0',
+ 'UNIVERSAL::require' => '0',
+ 'UUID::Tiny' => '0',
+ 'XML::LibXML' => '0',
+ 'XML::LibXML::XPathContext' => '0',
+ 'XML::LibXSLT' => '0',
+ 'XML::Simple' => '0',
+ }
+);
+
+$build->create_build_script;
+
+# vim:et:ts=4:sw=4:
--- /dev/null
+Build.PL
+lib/OpenILS.pm
+lib/OpenILS/Application.pm
+lib/OpenILS/Application/Acq.pm
+lib/OpenILS/Application/Acq/Claims.pm
+lib/OpenILS/Application/Acq/EDI.pm
+lib/OpenILS/Application/Acq/EDI/Translator.pm
+lib/OpenILS/Application/Acq/Financials.pm
+lib/OpenILS/Application/Acq/Invoice.pm
+lib/OpenILS/Application/Acq/Lineitem.pm
+lib/OpenILS/Application/Acq/Order.pm
+lib/OpenILS/Application/Acq/Picklist.pm
+lib/OpenILS/Application/Acq/Provider.pm
+lib/OpenILS/Application/Acq/Search.pm
+lib/OpenILS/Application/Actor.pm
+lib/OpenILS/Application/Actor/ClosedDates.pm
+lib/OpenILS/Application/Actor/Container.pm
+lib/OpenILS/Application/Actor/Friends.pm
+lib/OpenILS/Application/Actor/Stage.pm
+lib/OpenILS/Application/Actor/UserGroups.pm
+lib/OpenILS/Application/AppUtils.pm
+lib/OpenILS/Application/Booking.pm
+lib/OpenILS/Application/Cat.pm
+lib/OpenILS/Application/Cat/AssetCommon.pm
+lib/OpenILS/Application/Cat/AuthCommon.pm
+lib/OpenILS/Application/Cat/Authority.pm
+lib/OpenILS/Application/Cat/BibCommon.pm
+lib/OpenILS/Application/Cat/Merge.pm
+lib/OpenILS/Application/Circ.pm
+lib/OpenILS/Application/Circ/CircCommon.pm
+lib/OpenILS/Application/Circ/Circulate.pm
+lib/OpenILS/Application/Circ/CopyLocations.pm
+lib/OpenILS/Application/Circ/CreditCard.pm
+lib/OpenILS/Application/Circ/HoldNotify.pm
+lib/OpenILS/Application/Circ/Holds.pm
+lib/OpenILS/Application/Circ/Money.pm
+lib/OpenILS/Application/Circ/NonCat.pm
+lib/OpenILS/Application/Circ/ScriptBuilder.pm
+lib/OpenILS/Application/Circ/StatCat.pm
+lib/OpenILS/Application/Circ/Survey.pm
+lib/OpenILS/Application/Circ/Transit.pm
+lib/OpenILS/Application/Collections.pm
+lib/OpenILS/Application/Fielder.pm
+lib/OpenILS/Application/Ingest.pm
+lib/OpenILS/Application/Penalty.pm
+lib/OpenILS/Application/PermaCrud.pm
+lib/OpenILS/Application/Proxy.pm
+lib/OpenILS/Application/Reporter.pm
+lib/OpenILS/Application/ResolverResolver.pm
+lib/OpenILS/Application/Search.pm
+lib/OpenILS/Application/Search/AddedContent.pm
+lib/OpenILS/Application/Search/Authority.pm
+lib/OpenILS/Application/Search/Biblio.pm
+lib/OpenILS/Application/Search/CNBrowse.pm
+lib/OpenILS/Application/Search/Serial.pm
+lib/OpenILS/Application/Search/Z3950.pm
+lib/OpenILS/Application/Search/Zips.pm
+lib/OpenILS/Application/Serial.pm
+lib/OpenILS/Application/Storage.pm
+lib/OpenILS/Application/Storage/CDBI.pm
+lib/OpenILS/Application/Storage/CDBI/action.pm
+lib/OpenILS/Application/Storage/CDBI/actor.pm
+lib/OpenILS/Application/Storage/CDBI/asset.pm
+lib/OpenILS/Application/Storage/CDBI/authority.pm
+lib/OpenILS/Application/Storage/CDBI/biblio.pm
+lib/OpenILS/Application/Storage/CDBI/booking.pm
+lib/OpenILS/Application/Storage/CDBI/config.pm
+lib/OpenILS/Application/Storage/CDBI/container.pm
+lib/OpenILS/Application/Storage/CDBI/metabib.pm
+lib/OpenILS/Application/Storage/CDBI/money.pm
+lib/OpenILS/Application/Storage/CDBI/permission.pm
+lib/OpenILS/Application/Storage/CDBI/serial.pm
+lib/OpenILS/Application/Storage/Driver/Pg.pm
+lib/OpenILS/Application/Storage/Driver/Pg/cdbi.pm
+lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+lib/OpenILS/Application/Storage/Driver/Pg/fts.pm
+lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+lib/OpenILS/Application/Storage/Driver/Pg/storage.pm
+lib/OpenILS/Application/Storage/FTS.pm
+lib/OpenILS/Application/Storage/Publisher.pm
+lib/OpenILS/Application/Storage/Publisher/action.pm
+lib/OpenILS/Application/Storage/Publisher/actor.pm
+lib/OpenILS/Application/Storage/Publisher/asset.pm
+lib/OpenILS/Application/Storage/Publisher/authority.pm
+lib/OpenILS/Application/Storage/Publisher/biblio.pm
+lib/OpenILS/Application/Storage/Publisher/config.pm
+lib/OpenILS/Application/Storage/Publisher/container.pm
+lib/OpenILS/Application/Storage/Publisher/metabib.pm
+lib/OpenILS/Application/Storage/Publisher/money.pm
+lib/OpenILS/Application/Storage/Publisher/permission.pm
+lib/OpenILS/Application/Storage/QueryParser.pm
+lib/OpenILS/Application/SuperCat.pm
+lib/OpenILS/Application/Trigger.pm
+lib/OpenILS/Application/Trigger/Cleanup.pm
+lib/OpenILS/Application/Trigger/Collector.pm
+lib/OpenILS/Application/Trigger/Event.pm
+lib/OpenILS/Application/Trigger/EventGroup.pm
+lib/OpenILS/Application/Trigger/ModRunner.pm
+lib/OpenILS/Application/Trigger/Reactor.pm
+lib/OpenILS/Application/Trigger/Reactor/ApplyCircFee.pm
+lib/OpenILS/Application/Trigger/Reactor/ApplyPatronPenalty.pm
+lib/OpenILS/Application/Trigger/Reactor/AstCall.pm
+lib/OpenILS/Application/Trigger/Reactor/GeneratePurchaseOrderJEDI.pm
+lib/OpenILS/Application/Trigger/Reactor/MarkItemLost.pm
+lib/OpenILS/Application/Trigger/Reactor/ProcessTemplate.pm
+lib/OpenILS/Application/Trigger/Reactor/SendEmail.pm
+lib/OpenILS/Application/Trigger/Reactor/SendFile.pm
+lib/OpenILS/Application/Trigger/Reactor/StaticEmail.pm
+lib/OpenILS/Application/Trigger/Validator.pm
+lib/OpenILS/Application/Trigger/Validator/Acq.pm
+lib/OpenILS/Application/Trigger/Validator/Acq/PurchaseOrderEDIRequired.pm
+lib/OpenILS/Application/Trigger/Validator/Acq/UserRequestCancelled.pm
+lib/OpenILS/Application/Trigger/Validator/Acq/UserRequestOrdered.pm
+lib/OpenILS/Application/Trigger/Validator/Acq/UserRequestReceived.pm
+lib/OpenILS/Application/Vandelay.pm
+lib/OpenILS/Const.pm
+lib/OpenILS/Event.pm
+lib/OpenILS/Perm.pm
+lib/OpenILS/Reporter/Proxy.pm
+lib/OpenILS/Reporter/SQLBuilder.pm
+lib/OpenILS/SIP.pm
+lib/OpenILS/SIP/Item.pm
+lib/OpenILS/SIP/Msg.pm
+lib/OpenILS/SIP/Patron.pm
+lib/OpenILS/SIP/Transaction.pm
+lib/OpenILS/SIP/Transaction/Checkin.pm
+lib/OpenILS/SIP/Transaction/Checkout.pm
+lib/OpenILS/SIP/Transaction/Renew.pm
+lib/OpenILS/Template/Plugin/Unicode.pm
+lib/OpenILS/Template/Plugin/WebSession.pm
+lib/OpenILS/Template/Plugin/WebUtils.pm
+lib/OpenILS/Utils/Cronscript.pm
+lib/OpenILS/Utils/Cronscript.pm.in
+lib/OpenILS/Utils/CStoreEditor.pm
+lib/OpenILS/Utils/Editor.pm
+lib/OpenILS/Utils/Fieldmapper.pm
+lib/OpenILS/Utils/ISBN.pm
+lib/OpenILS/Utils/Lockfile.pm
+lib/OpenILS/Utils/MFHD.pm
+lib/OpenILS/Utils/MFHD/Caption.pm
+lib/OpenILS/Utils/MFHD/Date.pm
+lib/OpenILS/Utils/MFHD/Holding.pm
+lib/OpenILS/Utils/MFHD/test/mfhd.t
+lib/OpenILS/Utils/MFHD/test/mfhddata.txt
+lib/OpenILS/Utils/MFHD/test/testlib.pm
+lib/OpenILS/Utils/MFHDParser.pm
+lib/OpenILS/Utils/ModsParser.pm
+lib/OpenILS/Utils/Normalize.pm
+lib/OpenILS/Utils/OfflineStore.pm
+lib/OpenILS/Utils/Penalty.pm
+lib/OpenILS/Utils/PermitHold.pm
+lib/OpenILS/Utils/RemoteAccount.pm
+lib/OpenILS/Utils/ScriptRunner.pm
+lib/OpenILS/Utils/SpiderMonkey.pm
+lib/OpenILS/Utils/ZClient.pm
+lib/OpenILS/WWW/AddedContent.pm
+lib/OpenILS/WWW/AddedContent/Amazon.pm
+lib/OpenILS/WWW/AddedContent/ContentCafe.pm
+lib/OpenILS/WWW/AddedContent/OpenLibrary.pm
+lib/OpenILS/WWW/AddedContent/Syndetic.pm
+lib/OpenILS/WWW/BadDebt.pm
+lib/OpenILS/WWW/EGWeb.pm
+lib/OpenILS/WWW/Exporter.pm
+lib/OpenILS/WWW/IDL2js.pm
+lib/OpenILS/WWW/Method.pm
+lib/OpenILS/WWW/PasswordReset.pm
+lib/OpenILS/WWW/Proxy.pm
+lib/OpenILS/WWW/Redirect.pm
+lib/OpenILS/WWW/Reporter.pm
+lib/OpenILS/WWW/Reporter/transforms.pm
+lib/OpenILS/WWW/SuperCat.pm
+lib/OpenILS/WWW/SuperCat/Feed.pm
+lib/OpenILS/WWW/TemplateBatchBibUpdate.pm
+lib/OpenILS/WWW/Vandelay.pm
+lib/OpenILS/WWW/Web.pm
+lib/OpenILS/WWW/XMLRPCGateway.pm
+MANIFEST This list of files
--- /dev/null
+
+#!start included /usr/share/perl5/ExtUtils/MANIFEST.SKIP
+# Avoid version control files.
+\bRCS\b
+\bCVS\b
+\bSCCS\b
+,v$
+\B\.svn\b
+\B\.git\b
+\B\.gitignore\b
+\b_darcs\b
+\B\.cvsignore$
+
+# Avoid VMS specific MakeMaker generated files
+\bDescrip.MMS$
+\bDESCRIP.MMS$
+\bdescrip.mms$
+
+# Avoid Makemaker generated and utility files.
+\bMANIFEST\.bak
+\bMakefile$
+\bblib/
+\bMakeMaker-\d
+\bpm_to_blib\.ts$
+\bpm_to_blib$
+\bblibdirs\.ts$ # 6.18 through 6.25 generated this
+
+# Avoid Module::Build generated and utility files.
+\bBuild$
+\b_build/
+\bBuild.bat$
+\bBuild.COM$
+\bBUILD.COM$
+\bbuild.com$
+
+# Avoid temp and backup files.
+~$
+\.old$
+\#$
+\b\.#
+\.bak$
+\.tmp$
+\.#
+\.rej$
+\.swp$
+
+# Avoid OS-specific files/dirs
+# Mac OSX metadata
+\B\.DS_Store
+# Mac OSX SMB mount metadata files
+\B\._
+
+# Avoid Devel::Cover files.
+\bcover_db\b
+#!end included /usr/share/perl5/ExtUtils/MANIFEST.SKIP
+
+# Avoid configuration metadata file
+^MYMETA\.
+
+# Avoid Module::Build generated and utility files.
+\bBuild$
+\bBuild.bat$
+\b_build
+\bBuild.COM$
+\bBUILD.COM$
+\bbuild.com$
+^MANIFEST\.SKIP
+
+# Avoid archives of this distribution
+\bOpenILS-[\d\.\_]+
--- /dev/null
+---
+abstract: ~
+author: []
+configure_requires:
+ Module::Build: 0.36
+dynamic_config: 0
+generated_by: 'Module::Build version 0.3603'
+license: gpl
+meta-spec:
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: 1.4
+name: OpenILS
+provides:
+ CQL::BooleanNode:
+ file: lib/OpenILS/WWW/SuperCat.pm
+ CQL::TermNode:
+ file: lib/OpenILS/WWW/SuperCat.pm
+ Class::DBI:
+ file: lib/OpenILS/Application/Storage/FTS.pm
+ Fieldmapper:
+ file: lib/OpenILS/Utils/Fieldmapper.pm
+ MFHD:
+ file: lib/OpenILS/Utils/MFHD.pm
+ MFHD::Caption:
+ file: lib/OpenILS/Utils/MFHD/Caption.pm
+ MFHD::Date:
+ file: lib/OpenILS/Utils/MFHD/Date.pm
+ MFHD::Holding:
+ file: lib/OpenILS/Utils/MFHD/Holding.pm
+ OpenILS:
+ file: lib/OpenILS.pm
+ version: 2.00
+ OpenILS::Application:
+ file: lib/OpenILS/Application.pm
+ OpenILS::Application::Acq:
+ file: lib/OpenILS/Application/Acq.pm
+ OpenILS::Application::Acq::BatchManager:
+ file: lib/OpenILS/Application/Acq/Order.pm
+ OpenILS::Application::Acq::Claims:
+ file: lib/OpenILS/Application/Acq/Claims.pm
+ OpenILS::Application::Acq::EDI:
+ file: lib/OpenILS/Application/Acq/EDI.pm
+ OpenILS::Application::Acq::EDI::Translator:
+ file: lib/OpenILS/Application/Acq/EDI/Translator.pm
+ OpenILS::Application::Acq::Financials:
+ file: lib/OpenILS/Application/Acq/Financials.pm
+ OpenILS::Application::Acq::Invoice:
+ file: lib/OpenILS/Application/Acq/Invoice.pm
+ OpenILS::Application::Acq::Lineitem:
+ file: lib/OpenILS/Application/Acq/Lineitem.pm
+ OpenILS::Application::Acq::Order:
+ file: lib/OpenILS/Application/Acq/Order.pm
+ OpenILS::Application::Acq::Picklist:
+ file: lib/OpenILS/Application/Acq/Picklist.pm
+ OpenILS::Application::Acq::Provider:
+ file: lib/OpenILS/Application/Acq/Provider.pm
+ OpenILS::Application::Acq::Search:
+ file: lib/OpenILS/Application/Acq/Search.pm
+ OpenILS::Application::Actor:
+ file: lib/OpenILS/Application/Actor.pm
+ OpenILS::Application::Actor::ClosedDates:
+ file: lib/OpenILS/Application/Actor/ClosedDates.pm
+ OpenILS::Application::Actor::Container:
+ file: lib/OpenILS/Application/Actor/Container.pm
+ OpenILS::Application::Actor::Friends:
+ file: lib/OpenILS/Application/Actor/Friends.pm
+ OpenILS::Application::Actor::Stage:
+ file: lib/OpenILS/Application/Actor/Stage.pm
+ OpenILS::Application::Actor::UserGroups:
+ file: lib/OpenILS/Application/Actor/UserGroups.pm
+ OpenILS::Application::AppUtils:
+ file: lib/OpenILS/Application/AppUtils.pm
+ OpenILS::Application::Booking:
+ file: lib/OpenILS/Application/Booking.pm
+ OpenILS::Application::Cat:
+ file: lib/OpenILS/Application/Cat.pm
+ OpenILS::Application::Cat::AssetCommon:
+ file: lib/OpenILS/Application/Cat/AssetCommon.pm
+ OpenILS::Application::Cat::AuthCommon:
+ file: lib/OpenILS/Application/Cat/AuthCommon.pm
+ OpenILS::Application::Cat::Authority:
+ file: lib/OpenILS/Application/Cat/Authority.pm
+ OpenILS::Application::Cat::BibCommon:
+ file: lib/OpenILS/Application/Cat/BibCommon.pm
+ OpenILS::Application::Cat::Merge:
+ file: lib/OpenILS/Application/Cat/Merge.pm
+ OpenILS::Application::Circ:
+ file: lib/OpenILS/Application/Circ.pm
+ OpenILS::Application::Circ::CircCommon:
+ file: lib/OpenILS/Application/Circ/CircCommon.pm
+ OpenILS::Application::Circ::Circulate:
+ file: lib/OpenILS/Application/Circ/Circulate.pm
+ OpenILS::Application::Circ::Circulator:
+ file: lib/OpenILS/Application/Circ/Circulate.pm
+ OpenILS::Application::Circ::CopyLocations:
+ file: lib/OpenILS/Application/Circ/CopyLocations.pm
+ OpenILS::Application::Circ::CreditCard:
+ file: lib/OpenILS/Application/Circ/CreditCard.pm
+ OpenILS::Application::Circ::HoldNotify:
+ file: lib/OpenILS/Application/Circ/HoldNotify.pm
+ OpenILS::Application::Circ::Holds:
+ file: lib/OpenILS/Application/Circ/Holds.pm
+ OpenILS::Application::Circ::Money:
+ file: lib/OpenILS/Application/Circ/Money.pm
+ OpenILS::Application::Circ::NonCat:
+ file: lib/OpenILS/Application/Circ/NonCat.pm
+ OpenILS::Application::Circ::ScriptBuilder:
+ file: lib/OpenILS/Application/Circ/ScriptBuilder.pm
+ OpenILS::Application::Circ::StatCat:
+ file: lib/OpenILS/Application/Circ/StatCat.pm
+ OpenILS::Application::Circ::Survey:
+ file: lib/OpenILS/Application/Circ/Survey.pm
+ OpenILS::Application::Circ::Transit:
+ file: lib/OpenILS/Application/Circ/Transit.pm
+ OpenILS::Application::Collections:
+ file: lib/OpenILS/Application/Collections.pm
+ OpenILS::Application::Fielder:
+ file: lib/OpenILS/Application/Fielder.pm
+ OpenILS::Application::Ingest:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Ingest::Authority:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Ingest::Biblio:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Ingest::Biblio::Fingerprint:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Ingest::Biblio::URI:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Ingest::FlatMARC:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Ingest::XPATH:
+ file: lib/OpenILS/Application/Ingest.pm
+ OpenILS::Application::Penalty:
+ file: lib/OpenILS/Application/Penalty.pm
+ OpenILS::Application::PermaCrud:
+ file: lib/OpenILS/Application/PermaCrud.pm
+ OpenILS::Application::Proxy:
+ file: lib/OpenILS/Application/Proxy.pm
+ OpenILS::Application::Reporter:
+ file: lib/OpenILS/Application/Reporter.pm
+ OpenILS::Application::ResolverResolver:
+ file: lib/OpenILS/Application/ResolverResolver.pm
+ OpenILS::Application::Search:
+ file: lib/OpenILS/Application/Search.pm
+ OpenILS::Application::Search::AddedContent:
+ file: lib/OpenILS/Application/Search/AddedContent.pm
+ OpenILS::Application::Search::Authority:
+ file: lib/OpenILS/Application/Search/Authority.pm
+ OpenILS::Application::Search::Biblio:
+ file: lib/OpenILS/Application/Search/Biblio.pm
+ OpenILS::Application::Search::CNBrowse:
+ file: lib/OpenILS/Application/Search/CNBrowse.pm
+ OpenILS::Application::Search::Serial:
+ file: lib/OpenILS/Application/Search/Serial.pm
+ OpenILS::Application::Search::Z3950:
+ file: lib/OpenILS/Application/Search/Z3950.pm
+ OpenILS::Application::Search::Zips:
+ file: lib/OpenILS/Application/Search/Zips.pm
+ OpenILS::Application::Serial:
+ file: lib/OpenILS/Application/Serial.pm
+ OpenILS::Application::Storage:
+ file: lib/OpenILS/Application/Storage.pm
+ OpenILS::Application::Storage::CDBI:
+ file: lib/OpenILS/Application/Storage/CDBI.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::action:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::actor:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::asset:
+ file: lib/OpenILS/Application/Storage/CDBI/asset.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::authority:
+ file: lib/OpenILS/Application/Storage/CDBI/authority.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::biblio:
+ file: lib/OpenILS/Application/Storage/CDBI/biblio.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::booking:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::config:
+ file: lib/OpenILS/Application/Storage/CDBI/config.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::container:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::metabib:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::money:
+ file: lib/OpenILS/Application/Storage/CDBI/money.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::permission:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ version: 1
+ OpenILS::Application::Storage::CDBI::serial:
+ file: lib/OpenILS/Application/Storage/CDBI/serial.pm
+ version: 1
+ OpenILS::Application::Storage::Driver::Pg:
+ file: lib/OpenILS/Application/Storage/Driver/Pg.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::facet:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::filter:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::modifier:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::node:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::Driver::Pg::QueryParser::query_plan::node::atom:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
+ OpenILS::Application::Storage::FTS:
+ file: lib/OpenILS/Application/Storage/FTS.pm
+ OpenILS::Application::Storage::Publisher:
+ file: lib/OpenILS/Application/Storage/Publisher.pm
+ version: 1
+ OpenILS::Application::Storage::Publisher::action:
+ file: lib/OpenILS/Application/Storage/Publisher/action.pm
+ OpenILS::Application::Storage::Publisher::actor:
+ file: lib/OpenILS/Application/Storage/Publisher/actor.pm
+ OpenILS::Application::Storage::Publisher::asset:
+ file: lib/OpenILS/Application/Storage/Publisher/asset.pm
+ OpenILS::Application::Storage::Publisher::authority:
+ file: lib/OpenILS/Application/Storage/Publisher/authority.pm
+ version: 1
+ OpenILS::Application::Storage::Publisher::biblio:
+ file: lib/OpenILS/Application/Storage/Publisher/biblio.pm
+ version: 1
+ OpenILS::Application::Storage::Publisher::config:
+ file: lib/OpenILS/Application/Storage/Publisher/config.pm
+ OpenILS::Application::Storage::Publisher::container:
+ file: lib/OpenILS/Application/Storage/Publisher/container.pm
+ OpenILS::Application::Storage::Publisher::metabib:
+ file: lib/OpenILS/Application/Storage/Publisher/metabib.pm
+ version: 1
+ OpenILS::Application::Storage::Publisher::money:
+ file: lib/OpenILS/Application/Storage/Publisher/money.pm
+ OpenILS::Application::Storage::Publisher::permission:
+ file: lib/OpenILS/Application/Storage/Publisher/permission.pm
+ OpenILS::Application::SuperCat:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::acn:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::acp:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::auri:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sbsum:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::scap:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sdist:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::siss:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sisum:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sitem:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sssum:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sstr:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::ssub:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::ssum_base:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::SuperCat::unAPI::sunit:
+ file: lib/OpenILS/Application/SuperCat.pm
+ OpenILS::Application::Trigger:
+ file: lib/OpenILS/Application/Trigger.pm
+ OpenILS::Application::Trigger::Cleanup:
+ file: lib/OpenILS/Application/Trigger/Cleanup.pm
+ OpenILS::Application::Trigger::Collector:
+ file: lib/OpenILS/Application/Trigger/Collector.pm
+ OpenILS::Application::Trigger::Event:
+ file: lib/OpenILS/Application/Trigger/Event.pm
+ OpenILS::Application::Trigger::EventGroup:
+ file: lib/OpenILS/Application/Trigger/EventGroup.pm
+ OpenILS::Application::Trigger::ModLoader:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::ModRunner:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::ModRunner::Cleanup:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::ModRunner::Collector:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::ModRunner::Reactor:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::ModRunner::Validator:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::ModStackRunner:
+ file: lib/OpenILS/Application/Trigger/ModRunner.pm
+ OpenILS::Application::Trigger::Reactor:
+ file: lib/OpenILS/Application/Trigger/Reactor.pm
+ OpenILS::Application::Trigger::Reactor::ApplyCircFee:
+ file: lib/OpenILS/Application/Trigger/Reactor/ApplyCircFee.pm
+ OpenILS::Application::Trigger::Reactor::ApplyPatronPenalty:
+ file: lib/OpenILS/Application/Trigger/Reactor/ApplyPatronPenalty.pm
+ OpenILS::Application::Trigger::Reactor::AstCall:
+ file: lib/OpenILS/Application/Trigger/Reactor/AstCall.pm
+ OpenILS::Application::Trigger::Reactor::GeneratePurchaseOrderJEDI:
+ file: lib/OpenILS/Application/Trigger/Reactor/GeneratePurchaseOrderJEDI.pm
+ OpenILS::Application::Trigger::Reactor::MarkItemLost:
+ file: lib/OpenILS/Application/Trigger/Reactor/MarkItemLost.pm
+ OpenILS::Application::Trigger::Reactor::ProcessTemplate:
+ file: lib/OpenILS/Application/Trigger/Reactor/ProcessTemplate.pm
+ OpenILS::Application::Trigger::Reactor::SendEmail:
+ file: lib/OpenILS/Application/Trigger/Reactor/SendEmail.pm
+ OpenILS::Application::Trigger::Reactor::SendFile:
+ file: lib/OpenILS/Application/Trigger/Reactor/SendFile.pm
+ OpenILS::Application::Trigger::Reactor::StaticEmail:
+ file: lib/OpenILS/Application/Trigger/Reactor/StaticEmail.pm
+ OpenILS::Application::Trigger::Validator:
+ file: lib/OpenILS/Application/Trigger/Validator.pm
+ OpenILS::Application::Trigger::Validator::Acq:
+ file: lib/OpenILS/Application/Trigger/Validator/Acq.pm
+ OpenILS::Application::Trigger::Validator::Acq::PurchaseOrderEDIRequired:
+ file: lib/OpenILS/Application/Trigger/Validator/Acq/PurchaseOrderEDIRequired.pm
+ OpenILS::Application::Trigger::Validator::Acq::UserRequestCancelled:
+ file: lib/OpenILS/Application/Trigger/Validator/Acq/UserRequestCancelled.pm
+ OpenILS::Application::Trigger::Validator::Acq::UserRequestOrdered:
+ file: lib/OpenILS/Application/Trigger/Validator/Acq/UserRequestOrdered.pm
+ OpenILS::Application::Trigger::Validator::Acq::UserRequestReceived:
+ file: lib/OpenILS/Application/Trigger/Validator/Acq/UserRequestReceived.pm
+ OpenILS::Application::Vandelay:
+ file: lib/OpenILS/Application/Vandelay.pm
+ OpenILS::Const:
+ file: lib/OpenILS/Const.pm
+ OpenILS::Event:
+ file: lib/OpenILS/Event.pm
+ OpenILS::Perm:
+ file: lib/OpenILS/Perm.pm
+ OpenILS::Reporter::Proxy:
+ file: lib/OpenILS/Reporter/Proxy.pm
+ OpenILS::Reporter::SQLBuilder:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Having:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::OrderBy:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Select:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::Bare:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::GenericTransform:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::age:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::average:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::count:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::count_distinct:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::date_trunc:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::day_name:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::dom:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::dow:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::doy:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::first:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::hod:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::hour_trunc:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::last:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::lower:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::max:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::min:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::month_name:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::month_trunc:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::months_ago:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::moy:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::qoy:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::quarter:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::quarters_ago:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::substring:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::sum:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::upper:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::woy:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Transform::year_trunc:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Column::Where:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::Bare:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::GenericTransform:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::NULL:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::age:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::relative_date:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::relative_month:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::relative_week:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Input::Transform::relative_year:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Join:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Join::cross:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Join::inner:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Join::left:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Join::right:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::Relation:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::Reporter::SQLBuilder::ResultSet:
+ file: lib/OpenILS/Reporter/SQLBuilder.pm
+ OpenILS::SIP:
+ file: lib/OpenILS/SIP.pm
+ OpenILS::SIP::Item:
+ file: lib/OpenILS/SIP/Item.pm
+ OpenILS::SIP::Msg:
+ file: lib/OpenILS/SIP/Msg.pm
+ OpenILS::SIP::Patron:
+ file: lib/OpenILS/SIP/Patron.pm
+ OpenILS::SIP::Transaction:
+ file: lib/OpenILS/SIP/Transaction.pm
+ OpenILS::SIP::Transaction::Checkin:
+ file: lib/OpenILS/SIP/Transaction/Checkin.pm
+ OpenILS::SIP::Transaction::Checkout:
+ file: lib/OpenILS/SIP/Transaction/Checkout.pm
+ OpenILS::SIP::Transaction::Renew:
+ file: lib/OpenILS/SIP/Transaction/Renew.pm
+ OpenILS::Template::Plugin::Unicode:
+ file: lib/OpenILS/Template/Plugin/Unicode.pm
+ OpenILS::Template::Plugin::WebSession:
+ file: lib/OpenILS/Template/Plugin/WebSession.pm
+ OpenILS::Template::Plugin::WebUtils:
+ file: lib/OpenILS/Template/Plugin/WebUtils.pm
+ OpenILS::Utils::CStoreEditor:
+ file: lib/OpenILS/Utils/CStoreEditor.pm
+ OpenILS::Utils::Cronscript:
+ file: lib/OpenILS/Utils/Cronscript.pm
+ OpenILS::Utils::Editor:
+ file: lib/OpenILS/Utils/Editor.pm
+ OpenILS::Utils::ISBN:
+ file: lib/OpenILS/Utils/ISBN.pm
+ version: 0.01
+ OpenILS::Utils::Lockfile:
+ file: lib/OpenILS/Utils/Lockfile.pm
+ OpenILS::Utils::MFHDParser:
+ file: lib/OpenILS/Utils/MFHDParser.pm
+ OpenILS::Utils::ModsParser:
+ file: lib/OpenILS/Utils/ModsParser.pm
+ OpenILS::Utils::Normalize:
+ file: lib/OpenILS/Utils/Normalize.pm
+ OpenILS::Utils::OfflineStore:
+ file: lib/OpenILS/Utils/OfflineStore.pm
+ OpenILS::Utils::OfflineStore::Script:
+ file: lib/OpenILS/Utils/OfflineStore.pm
+ OpenILS::Utils::OfflineStore::Session:
+ file: lib/OpenILS/Utils/OfflineStore.pm
+ OpenILS::Utils::Penalty:
+ file: lib/OpenILS/Utils/Penalty.pm
+ OpenILS::Utils::PermitHold:
+ file: lib/OpenILS/Utils/PermitHold.pm
+ OpenILS::Utils::RemoteAccount:
+ file: lib/OpenILS/Utils/RemoteAccount.pm
+ OpenILS::Utils::ScriptRunner:
+ file: lib/OpenILS/Utils/ScriptRunner.pm
+ OpenILS::Utils::SpiderMonkey:
+ file: lib/OpenILS/Utils/SpiderMonkey.pm
+ OpenILS::Utils::ZClient:
+ file: lib/OpenILS/Utils/ZClient.pm
+ OpenILS::Utils::ZClient::Record:
+ file: lib/OpenILS/Utils/ZClient.pm
+ OpenILS::Utils::ZClient::ResultSet:
+ file: lib/OpenILS/Utils/ZClient.pm
+ OpenILS::WWW::AddedContent:
+ file: lib/OpenILS/WWW/AddedContent.pm
+ OpenILS::WWW::AddedContent::Amazon:
+ file: lib/OpenILS/WWW/AddedContent/Amazon.pm
+ OpenILS::WWW::AddedContent::ContentCafe:
+ file: lib/OpenILS/WWW/AddedContent/ContentCafe.pm
+ OpenILS::WWW::AddedContent::OpenLibrary:
+ file: lib/OpenILS/WWW/AddedContent/OpenLibrary.pm
+ OpenILS::WWW::AddedContent::Syndetic:
+ file: lib/OpenILS/WWW/AddedContent/Syndetic.pm
+ OpenILS::WWW::BadDebt:
+ file: lib/OpenILS/WWW/BadDebt.pm
+ OpenILS::WWW::EGWeb:
+ file: lib/OpenILS/WWW/EGWeb.pm
+ OpenILS::WWW::Exporter:
+ file: lib/OpenILS/WWW/Exporter.pm
+ OpenILS::WWW::IDL2js:
+ file: lib/OpenILS/WWW/IDL2js.pm
+ OpenILS::WWW::Method:
+ file: lib/OpenILS/WWW/Method.pm
+ OpenILS::WWW::PasswordReset:
+ file: lib/OpenILS/WWW/PasswordReset.pm
+ OpenILS::WWW::Proxy:
+ file: lib/OpenILS/WWW/Proxy.pm
+ OpenILS::WWW::Redirect:
+ file: lib/OpenILS/WWW/Redirect.pm
+ OpenILS::WWW::Reporter:
+ file: lib/OpenILS/WWW/Reporter.pm
+ OpenILS::WWW::SuperCat:
+ file: lib/OpenILS/WWW/SuperCat.pm
+ OpenILS::WWW::SuperCat::Feed:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::atom:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::atom::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::html:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::html::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::htmlcard:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::htmlcard::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::htmlholdings:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::htmlholdings::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::marctxt:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::marctxt::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::marcxml:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::marcxml::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods3:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods32:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods32::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods33:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods33::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods3::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::mods::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::ris:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::ris::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::rss2:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::SuperCat::Feed::rss2::item:
+ file: lib/OpenILS/WWW/SuperCat/Feed.pm
+ OpenILS::WWW::TemplateBatchBibUpdate:
+ file: lib/OpenILS/WWW/TemplateBatchBibUpdate.pm
+ OpenILS::WWW::Vandelay:
+ file: lib/OpenILS/WWW/Vandelay.pm
+ OpenILS::WWW::Web:
+ file: lib/OpenILS/WWW/Web.pm
+ OpenILS::WWW::XMLRPCGateway:
+ file: lib/OpenILS/WWW/XMLRPCGateway.pm
+ PathConfig:
+ file: lib/OpenILS/WWW/EGWeb.pm
+ QueryParser:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ QueryParser::query_plan:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ QueryParser::query_plan::facet:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ QueryParser::query_plan::filter:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ QueryParser::query_plan::modifier:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ QueryParser::query_plan::node:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ QueryParser::query_plan::node::atom:
+ file: lib/OpenILS/Application/Storage/QueryParser.pm
+ action:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::circulation:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::hold_copy_map:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::hold_notification:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::hold_request:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::hold_transit_copy:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::in_house_use:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::non_cat_in_house_use:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::non_cataloged_circulation:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::open_circulation:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::reservation_transit_copy:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::survey:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::survey_answer:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::survey_question:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::survey_response:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::transit_copy:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ action::unfulfilled_hold_list:
+ file: lib/OpenILS/Application/Storage/CDBI/action.pm
+ actor:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ actor::card:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_address:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_unit:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_unit::closed_date:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_unit::hours_of_operation:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_unit_proximity:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_unit_setting:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::org_unit_type:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::perm_group:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ actor::perm_group_permission_map:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ actor::perm_group_user_map:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ actor::permission:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ actor::stat_cat:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::stat_cat_entry:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::stat_cat_entry_user_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::user:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::user_access_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/actor.pm
+ actor::user_address:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::user_setting:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::user_standing_penalty:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::usr_note:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::usr_org_unit_opt_in:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ actor::workstation:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset:
+ file: lib/OpenILS/Application/Storage/CDBI/asset.pm
+ asset::call_number:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::call_number_class:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::call_number_note:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::copy:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::copy_location:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::copy_location_order:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::copy_note:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::stat_cat:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::stat_cat_entry:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ asset::stat_cat_entry_copy_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ authority:
+ file: lib/OpenILS/Application/Storage/CDBI/authority.pm
+ authority::full_rec:
+ file: lib/OpenILS/Application/Storage/CDBI/authority.pm
+ authority::record_descriptor:
+ file: lib/OpenILS/Application/Storage/CDBI/authority.pm
+ authority::record_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/authority.pm
+ authority::record_note:
+ file: lib/OpenILS/Application/Storage/CDBI/authority.pm
+ biblio:
+ file: lib/OpenILS/Application/Storage/CDBI/biblio.pm
+ biblio::record_entry:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ biblio::record_note:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ booking:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ booking::reservation:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ booking::reservation_attr_value_map:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ booking::resource:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ booking::resource_attr_map:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ booking::resource_type:
+ file: lib/OpenILS/Application/Storage/CDBI/booking.pm
+ config:
+ file: lib/OpenILS/Application/Storage/CDBI/config.pm
+ config::audience_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::bib_source:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::copy_status:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::i18n_core:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::i18n_locale:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::identification_type:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::item_form_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::item_type_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::language_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::lit_form_map:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::metabib_field:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::net_access_level:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::non_cataloged_type:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::rules::age_hold_protect:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::rules::circ_duration:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::rules::max_fine:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::rules::recurring_fine:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ config::standing:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ container:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::biblio_record_entry_bucket:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::biblio_record_entry_bucket_item:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::call_number_bucket:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::call_number_bucket_item:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::copy_bucket:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::copy_bucket_item:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::user_bucket:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ container::user_bucket_item:
+ file: lib/OpenILS/Application/Storage/CDBI/container.pm
+ metabib:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::author_field_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::full_rec:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::identifier_field_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::keyword_field_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::metarecord:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::metarecord_source_map:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::record_descriptor:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::series_field_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::subject_field_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ metabib::title_field_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/metabib.pm
+ money:
+ file: lib/OpenILS/Application/Storage/CDBI/money.pm
+ money::billable_transaction:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::billable_transaction_summary:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::billing:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::cash_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::check_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::collections_tracker:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::credit_card_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::credit_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::desk_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::forgive_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::goods_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::grocery:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::open_billable_transaction_summary:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::open_user_circulation_summary:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::open_user_summary:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::user_circulation_summary:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::user_summary:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ money::work_payment:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ permission:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ permission::grp_perm_map:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ permission::grp_tree:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ permission::perm_list:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ permission::usr_grp_map:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ permission::usr_perm_map:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ permission::usr_work_ou_map:
+ file: lib/OpenILS/Application/Storage/CDBI/permission.pm
+ serial:
+ file: lib/OpenILS/Application/Storage/CDBI/serial.pm
+ serial::issuance:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ serial::item:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ serial::record_entry:
+ file: lib/OpenILS/Application/Storage/CDBI/serial.pm
+ serial::subscription:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ serial::unit:
+ file: lib/OpenILS/Application/Storage/Driver/Pg/dbi.pm
+ testlib:
+ file: lib/OpenILS/Utils/MFHD/test/testlib.pm
+requires:
+ APR::Const: 0
+ APR::Table: 0
+ Apache2::Const: 0
+ Apache2::Log: 0
+ Apache2::RequestIO: 0
+ Apache2::RequestRec: 0
+ Apache2::RequestUtil: 0
+ Business::CreditCard: 0
+ Business::EDI: 0
+ Business::ISBN: 0
+ Business::OnlinePayment: 0
+ CGI: 0
+ Carp: 0
+ Class::DBI: 0
+ Class::DBI::AbstractSearch: 0
+ DBI: 0
+ Data::Dumper: 0
+ DateTime: 0
+ DateTime::Format::ISO8601: 0
+ DateTime::Format::Mail: 0
+ DateTime::Format::Strptime: 0
+ DateTime::Set: 0
+ DateTime::SpanSet: 0
+ Digest::MD5: 0
+ Email::Send: 0
+ Encode: 0
+ Error: 0
+ Exporter: 0
+ File::Basename: 0
+ File::Spec: 0
+ File::Temp: 0
+ File::stat: 0
+ Getopt::Long: 0
+ IO::Scalar: 0
+ JavaScript::SpiderMonkey: 0
+ LWP::UserAgent: 0
+ List::Util: 0
+ Locale::Country: 0
+ MARC::Batch: 0
+ MARC::Field: 0
+ MARC::File::XML: 0
+ MARC::Record: 0
+ MIME::Base64: 0
+ Net::FTP: 0
+ Net::SSH2: 0
+ OpenSRF::AppSession: 0
+ OpenSRF::Application: 0
+ OpenSRF::EX: 0
+ OpenSRF::MultiSession: 0
+ OpenSRF::System: 0
+ OpenSRF::Utils: 0
+ OpenSRF::Utils::Cache: 0
+ OpenSRF::Utils::Config: 0
+ OpenSRF::Utils::JSON: 0
+ OpenSRF::Utils::Logger: 0
+ OpenSRF::Utils::SettingsClient: 0
+ OpenSRF::Utils::SettingsParser: 0
+ POSIX: 0
+ Parse::RecDescent: 0
+ RPC::XML: 0
+ RPC::XML::Client: 0
+ RPC::XML::Function: 0
+ RPC::XML::Method: 0
+ RPC::XML::Parser: 0
+ RPC::XML::Procedure: 0
+ SRU::Request: 0
+ SRU::Response: 0
+ Safe: 0
+ Scalar::Util: 0
+ Socket: 0
+ Sys::Syslog: 0
+ Template: 0
+ Template::Plugin: 0
+ Test::More: 0
+ Text::Aspell: 0
+ Text::CSV: 0
+ Text::Glob: 0
+ Time::HiRes: 0
+ Time::Local: 0
+ UNIVERSAL::require: 0
+ UUID::Tiny: 0
+ Unicode::Normalize: 0
+ XML::LibXML: 0
+ XML::LibXML::XPathContext: 0
+ XML::LibXSLT: 0
+ XML::Simple: 0
+resources:
+ license: http://opensource.org/licenses/gpl-license.php
+version: 2.00
--- /dev/null
+# Copyright (C) 2009 Equinox Software, Inc.
+# Shawn Boyette <sboyette@esilibrary.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+CLEANFILES = Build
+DISTCLEANFILES = Makefile.in Makefile
+
+all:
+ perl Build.PL || make -s build-perl-fail
+ ./Build || make -s build-perl-fail
+
+check:
+ ./Build test || make -s build-perl-fail
+
+install:
+ ./Build install
+
+build-perl-fail:
+ echo
+ echo ">>> Build/test of Perl modules has failed. The most likely"
+ echo ">>> possibility is that a dependency is not pre-installed"
+ echo ">>> or that a test has failed."
+ echo ">>> See the messages above this one for more information."
+ echo
+ exit 1
+
+install-perl-fail:
+ echo
+ echo ">>> Install of Perl modules has failed."
+ echo ">>> Are you root?"
+ echo ">>> See the message above this one for more information."
+ echo
+
+distclean-local:
+ rm -rf ./_build
+ rm -rf ./blib
\ No newline at end of file
+++ /dev/null
-package OpenILS::Application;
-use OpenSRF::Application;
-use UNIVERSAL::require;
-use base qw/OpenSRF::Application/;
-
-sub ils_version {
- # version format is "x-y-z", for example "2-0-0" for Evergreen 2.0.0
- # For branches, format is "x-y"
- return "HEAD";
-}
-
-__PACKAGE__->register_method(
- api_name => 'opensrf.open-ils.system.ils_version',
- api_level => 1,
- method => 'ils_version',
-);
-
-__PACKAGE__->register_method(
- api_name => 'opensrf.open-ils.fetch_idl.file',
- api_level => 1,
- method => 'get_idl_file',
-);
-sub get_idl_file {
- use OpenSRF::Utils::SettingsClient;
- return OpenSRF::Utils::SettingsClient->new->config_value('IDL');
-}
-
-sub register_method {
- my $class = shift;
- my %args = @_;
- my %dup_args = %args;
-
- $class = ref($class) || $class;
-
- $args{package} ||= $class;
- __PACKAGE__->SUPER::register_method( %args );
-
- if (exists($dup_args{authoritative}) and $dup_args{authoritative}) {
- (my $name = $dup_args{api_name}) =~ s/$/.authoritative/o;
- if ($name ne $dup_args{api_name}) {
- $dup_args{real_api_name} = $dup_args{api_name};
- $dup_args{method} = 'authoritative_wrapper';
- $dup_args{api_name} = $name;
- $dup_args{package} = __PACKAGE__;
- __PACKAGE__->SUPER::register_method( %dup_args );
- }
- }
-}
-
-sub authoritative_wrapper {
-
- if (!$OpenILS::Utils::CStoreEditor::_loaded) {
- die "Couldn't load OpenILS::Utils::CStoreEditor!" unless 'OpenILS::Utils::CStoreEditor'->use;
- }
-
- my $self = shift;
- my $client = shift;
- my @args = @_;
-
- my $method = $self->method_lookup($self->{real_api_name});
- die unless $method;
-
- local $OpenILS::Utils::CStoreEditor::always_xact = 1;
-
- $client->respond( $_ ) for ( $method->run(@args) );
-
- OpenILS::Utils::CStoreEditor->flush_forced_xacts();
-
- return undef;
-}
-
-1;
-
+++ /dev/null
-package OpenILS::Application::Acq;
-use base qw/OpenILS::Application/;
-use strict; use warnings;
-
-use OpenILS::Application::Acq::Picklist;
-use OpenILS::Application::Acq::Financials;
-use OpenILS::Application::Acq::Provider;
-use OpenILS::Application::Acq::Lineitem;
-use OpenILS::Application::Acq::Order;
-use OpenILS::Application::Acq::EDI;
-use OpenILS::Application::Acq::Search;
-use OpenILS::Application::Acq::Claims;
-use OpenILS::Application::Acq::Invoice;
-
-1;
+++ /dev/null
-package OpenILS::Application::Acq::Claims;
-use base qw/OpenILS::Application/;
-use strict; use warnings;
-
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Event;
-my $U = 'OpenILS::Application::AppUtils';
-
-
-__PACKAGE__->register_method(
- method => 'claim_ready_items',
- api_name => 'open-ils.acq.claim.eligible.lineitem_detail',
- stream => 1,
- signature => {
- desc => q/Locates lineitem_details that are eligible for claiming/,
- params => [
- {desc => 'Authentication token', type => 'string'},
- { desc => q/
- Filter object. Filter keys include
- purchase_order
- lineitem
- lineitem_detail
- claim_policy_action
- ordering_agency
- /,
- type => 'object'
- },
- { desc => q/
- Flesh fields. Which fields to flesh on the response object.
- For valid options, see the filter object
- q/,
- type => 'array'
- }
- ],
- return => {desc => 'Claim ready data', type => 'object', class => 'acrlid'}
- }
-);
-
-sub claim_ready_items {
- my($self, $conn, $auth, $filters, $flesh_fields, $limit, $offset) = @_;
-
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
-
- $filters ||= {};
- $flesh_fields ||= [];
- $limit ||= 50;
- $offset ||= 0;
-
- if(defined $filters->{ordering_agency}) {
- return $e->event unless $e->allowed('VIEW_PURCHASE_ORDER', $filters->{ordering_agency});
- } else {
- $filters->{ordering_agency} = $U->user_has_work_perm_at($e, 'VIEW_PURCHASE_ORDER', {descendants => 1});
- }
-
- my $items = $e->search_acq_claim_ready_lineitem_detail([$filters, {limit => $limit, offset => $offset}]);
-
- my %cache;
- for my $item (@$items) {
-
- # flesh from the flesh fields, using the cache when we can
- foreach (@$flesh_fields) {
- my $retrieve = "retrieve_acq_${_}";
- $cache{$_} = {} unless $cache{$_};
- $item->$_(
- $cache{$_}{$item->$_} ||
- ($cache{$_}{$item->$_} = $e->$retrieve($item->$_))
- );
- }
-
- $conn->respond($item);
- }
-
- return undef;
-}
-
-__PACKAGE__->register_method(
- method => "claim_item",
- api_name => "open-ils.acq.claim.lineitem",
- stream => 1,
- signature => {
- desc => q/Initiates a claim for a lineitem/,
- params => [
- {desc => "Authentication token", type => "string"},
- {desc => "Lineitem ID", type => "number"},
- {desc => q/Claim (acqcl) ID. If defined, attach new claim
- events to this existing claim object/, type => "number"},
- {desc => q/Claim Type (acqclt) ID. If defined (and no claim is
- defined), create a new claim with this type/, type => "number"},
- {desc => "Note for the claim event", type => "string"},
- {desc => q/Optional: Claim Policy Actions. If not present,
- claim events for all eligible claim policy actions will be
- created. This is an array of acqclpa IDs./,
- type => "array"},
- ],
- return => {
- desc => "The claim voucher events on success, Event on error",
- type => "object", class => "acrlid"
- }
- }
-);
-
-__PACKAGE__->register_method(
- method => 'claim_item',
- api_name => 'open-ils.acq.claim.lineitem_detail',
- stream => 1,
- signature => {
- desc => q/Initiates a claim for an individual lineitem_detail/,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'Lineitem Detail ID', type => 'number'},
- {desc => 'Claim (acqcl) ID. If defined, attach new claim events to this existing claim object', type => 'number'},
- {desc => 'Claim Type (acqclt) ID. If defined (and no claim is defined), create a new claim with this type', type => 'number'},
- {desc => "Note for the claim event", type => "string"},
- { desc => q/
-
- Optional: Claim Policy Actions. If not present, claim events
- for all eligible claim policy actions will be created. This is
- an array of acqclpa ID's.
- /,
- type => 'array'
- },
- { desc => q/
- Optional: Claim Event Types. If present, we bypass any policy configuration
- and use the specified event types. This is useful for manual claiming against
- items that have no claim policy.
- /,
- type => 'array'
- }
- ],
- return => {
- desc => "The claim voucher events on success, Event on error",
- type => "object", class => "acrlid"
- }
- }
-);
-
-sub claim_item {
- my $self = shift;
- my $conn = shift;
- my $auth = shift;
- my $object_id = shift;
- my $claim_id = shift;
- my $claim_type_id = shift;
- my $note = shift;
- my $policy_actions = shift;
-
- # if this claim occurs outside of a policy, allow the caller to specificy the event type
- my $claim_event_types = shift;
-
- my $e = new_editor(xact => 1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- my $evt;
- my $claim;
- my $claim_type;
- my $claim_events = {
- events => [],
- trigger_stuff => []
- };
-
- my $lid_flesh = {
- "flesh" => 2,
- "flesh_fields" => {
- "acqlid" => ["lineitem"], "jub" => ["purchase_order"],
- }
- };
-
- if($claim_id) {
- $claim = $e->retrieve_acq_claim($claim_id) or return $e->die_event;
- } elsif($claim_type_id) {
- $claim_type = $e->retrieve_acq_claim_type($claim_type_id) or return $e->die_event;
- } else {
- $e->rollback;
- return OpenILS::Event->new('BAD_PARAMS');
- }
-
-
- my $lids;
- if($self->api_name =~ /claim.lineitem_detail/) {
-
- $lids = $e->search_acq_lineitem_detail([
- {"id" => $object_id, "cancel_reason" => undef},
- $lid_flesh
- ]) or return $e->die_event;
-
- } elsif($self->api_name =~ /claim.lineitem/) {
- $lids = $e->search_acq_lineitem_detail([
- {"lineitem" => $object_id, "cancel_reason" => undef},
- $lid_flesh
- ]) or return $e->die_event;
- }
-
- foreach my $lid (@$lids) {
- return $evt if
- $evt = claim_lineitem_detail(
- $e, $lid, $claim, $claim_type, $policy_actions,
- $note, $claim_events, $claim_event_types
- );
- }
-
- $e->commit;
-
- # create related A/T events
- $U->create_events_for_hook('claim_event.created', $_->[0], $_->[1]) for @{$claim_events->{trigger_stuff}};
-
- # do voucher rendering and return result
- $conn->respond($U->fire_object_event(
- undef, "format.acqcle.html", $_->[0], $_->[1], "print-on-demand"
- )) foreach @{$claim_events->{trigger_stuff}};
- return undef;
-}
-
-sub claim_lineitem_detail {
- my($e, $lid, $claim, $claim_type, $policy_actions, $note, $claim_events, $claim_event_types) = @_;
-
- # Create the claim object
- unless($claim) {
- $claim = Fieldmapper::acq::claim->new;
- $claim->lineitem_detail($lid->id);
- $claim->type($claim_type->id);
- $e->create_acq_claim($claim) or return $e->die_event;
- }
-
- unless($claim_event_types) {
- # user did not specify explicit event types
-
- unless($policy_actions) {
- # user did not specifcy policy actions. find all eligible.
-
- my $list = $e->json_query({
- select => {acrlid => ['claim_policy_action']},
- from => 'acrlid',
- where => {lineitem_detail => $lid->id}
- });
-
- $policy_actions = [map { $_->{claim_policy_action} } @$list];
- }
-
- # from the set of policy_action's, locate the related event types
- # IOW, the policy action's action
- $claim_event_types = [];
- for my $act_id (@$policy_actions) {
- my $action = $e->retrieve_acq_claim_policy_action($act_id) or return $e->die_event;
- push(@$claim_event_types, $action->action);
- }
- }
-
- # for each eligible (or chosen) policy actions, create a claim_event
- for my $event_type (@$claim_event_types) {
- my $event = Fieldmapper::acq::claim_event->new;
- $event->claim($claim->id);
- $event->type($event_type);
- $event->creator($e->requestor->id);
- $event->note($note);
- $e->create_acq_claim_event($event) or return $e->die_event;
- push(@{$claim_events->{events}}, $event);
- push(@{$claim_events->{trigger_stuff}}, [$event, $lid->lineitem->purchase_order->ordering_agency]);
- }
-
- return undef;
-}
-
-
-__PACKAGE__->register_method(
- method => "get_claim_voucher_by_lid",
- api_name => "open-ils.acq.claim.voucher.by_lineitem_detail",
- stream => 1,
- signature => {
- desc => q/Retrieve existing claim vouchers by lineitem detail ID/,
- params => [
- {desc => "Authentication token", type => "string"},
- {desc => "Lineitem detail ID", type => "number"}
- ],
- return => {
- desc => "Claim ready data", type => "object", class => "atev"
- }
- }
-);
-
-sub get_claim_voucher_by_lid {
- my ($self, $conn, $auth, $lid_id) = @_;
-
- my $e = new_editor("authtoken" => $auth);
- return $e->die_event unless $e->checkauth;
-
- my $lid = $e->retrieve_acq_lineitem_detail([
- $lid_id, {
- "flesh" => 2,
- "flesh_fields" => {
- "acqlid" => ["lineitem"], "jub" => ["purchase_order"]
- }
- }
- ]);
-
- return $e->die_event unless $e->allowed(
- "VIEW_PURCHASE_ORDER", $lid->lineitem->purchase_order->ordering_agency
- );
-
- my $id_list = $e->json_query({
- "select" => {"atev" => ["id"]},
- "from" => {
- "atev" => {
- "atevdef" => {"field" => "id", "fkey" => "event_def"},
- "acqcle" => {
- "field" => "id", "fkey" => "target",
- "join" => {
- "acqcl" => {
- "field" => "id", "fkey" => "claim",
- "join" => {
- "acqlid" => {
- "fkey" => "lineitem_detail",
- "field" => "id"
- }
- }
- }
- }
- }
- }
- },
- "where" => {
- "-and" => {
- "+atevdef" => {"hook" => "format.acqcle.html"},
- "+acqlid" => {"id" => $lid_id}
- }
- }
- }) or return $e->die_event;
-
- if ($id_list && @$id_list) {
- foreach (@$id_list) {
- $conn->respond(
- $e->retrieve_action_trigger_event([
- $_->{"id"}, {
- "flesh" => 1,
- "flesh_fields" => {"atev" => ["template_output"]}
- }
- ])
- );
- }
- }
-
- $e->disconnect;
- undef;
-}
-
-1;
+++ /dev/null
-package OpenILS::Application::Acq::EDI;
-use base qw/OpenILS::Application/;
-
-use strict; use warnings;
-
-use IO::Scalar;
-
-use OpenSRF::AppSession;
-use OpenSRF::EX qw/:try/;
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenSRF::Utils::JSON;
-
-use OpenILS::Application::Acq::Lineitem;
-use OpenILS::Utils::RemoteAccount;
-use OpenILS::Utils::CStoreEditor q/new_editor/;
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Application::Acq::EDI::Translator;
-
-use Business::EDI;
-
-use Data::Dumper;
-our $verbose = 0;
-
-sub new {
- my($class, %args) = @_;
- my $self = bless(\%args, $class);
- # $self->{args} = {};
- return $self;
-}
-
-# our $reasons = {}; # cache for acq.cancel_reason rows ?
-
-our $translator;
-
-sub translator {
- return $translator ||= OpenILS::Application::Acq::EDI::Translator->new(@_);
-}
-
-my %map = (
- host => 'remote_host',
- username => 'remote_user',
- password => 'remote_password',
- account => 'remote_account',
- # in_dir => 'remote_path', # field_map overrides path with in_dir
- path => 'remote_path',
-);
-
-
-## Just for debugging stuff:
-sub add_a_msg {
- my ($self, $conn) = @_;
- my $e = new_editor(xact=>1);
- my $incoming = Fieldmapper::acq::edi_message->new;
- $incoming->edi("This is content");
- $incoming->account(1);
- $incoming->remote_file('in/some_file.edi');
- $e->create_acq_edi_message($incoming);;
- $e->commit;
-}
-# __PACKAGE__->register_method( method => 'add_a_msg', api_name => 'open-ils.acq.edi.add_a_msg'); # debugging
-
-__PACKAGE__->register_method(
- method => 'retrieve',
- api_name => 'open-ils.acq.edi.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Fetch incoming message(s) from EDI accounts. ' .
- 'Optional arguments to restrict to one vendor and/or a max number of messages. ' .
- 'Note that messages are not parsed or processed here, just fetched and translated.',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'Vendor ID (undef for "all")', type => 'number'},
- {desc => 'Date Inactive Since', type => 'string'},
- {desc => 'Max Messages Retrieved', type => 'number'}
- ],
- return => {
- desc => 'List of new message IDs (empty if none)',
- type => 'array'
- }
- }
-);
-
-sub retrieve_core {
- my ($self, $set, $max, $e, $test) = @_; # $e is a working editor
-
- $e ||= new_editor();
- $set ||= __PACKAGE__->retrieve_vendors($e);
-
- my @return = ();
- my $vcount = 0;
- foreach my $account (@$set) {
- my $count = 0;
- my $server;
- $logger->info("EDI check for vendor " . ++$vcount . " of " . scalar(@$set) . ": " . $account->host);
- unless ($server = __PACKAGE__->remote_account($account)) { # assignment, not comparison
- $logger->err(sprintf "Failed remote account mapping for %s (%s)", $account->host, $account->id);
- next;
- };
-# my $rf_starter = './'; # default to current dir
- if ($account->in_dir) {
- if ($account->in_dir =~ /\*+.*\//) {
- $logger->err("EDI in_dir has a slash after an asterisk in value: '" . $account->in_dir . "'. Skipping account with indeterminate target dir!");
- next;
- }
-# $rf_starter = $account->in_dir;
-# $rf_starter =~ s/((\/)?[^\/]*)\*+[^\/]*$//; # kill up to the first (possible) slash before the asterisk: keep the preceeding static dir
-# $rf_starter .= '/' if $rf_starter or $2; # recap the dir, or replace leading "/" if there was one (but don't add if empty)
- }
- my @files = ($server->ls({remote_file => ($account->in_dir || './')}));
- my @ok_files = grep {$_ !~ /\/\.?\.$/ and $_ ne '0'} @files;
- $logger->info(sprintf "%s of %s files at %s/%s", scalar(@ok_files), scalar(@files), $account->host, $account->in_dir);
- # $server->remote_path(undef);
- foreach my $remote_file (@ok_files) {
- # my $remote_file = $rf_starter . $_;
- my $description = sprintf "%s/%s", $account->host, $remote_file;
-
- # deduplicate vs. acct/filenames already in DB
- my $hits = $e->search_acq_edi_message([
- {
- account => $account->id,
- remote_file => $remote_file,
- status => {'in' => [qw/ processed /]}, # if it never got processed, go ahead and get the new one (try again)
- # create_time => 'NOW() - 60 DAYS', # if we wanted to allow filenames to be reused after a certain time
- # ideally we would also use the date from FTP, but that info isn't available via RemoteAccount
- }
- # { flesh => 1, flesh_fields => {...}, }
- ]);
- if (scalar(@$hits)) {
- $logger->debug("EDI: $remote_file already retrieved. Skipping");
- warn "EDI: $remote_file already retrieved. Skipping";
- next;
- }
-
- ++$count;
- $max and $count > $max and last;
- $logger->info(sprintf "%s of %s targets: %s", $count, scalar(@ok_files), $description);
- print sprintf "%s of %s targets: %s\n", $count, scalar(@ok_files), $description;
- if ($test) {
- push @return, "test_$count";
- next;
- }
- my $content;
- my $io = IO::Scalar->new(\$content);
- unless ( $server->get({remote_file => $remote_file, local_file => $io}) ) {
- $logger->error("(S)FTP get($description) failed");
- next;
- }
- my $incoming = __PACKAGE__->process_retrieval($content, $remote_file, $server, $account->id, $e);
-# $server->delete(remote_file => $_); # delete remote copies of saved message
- push @return, $incoming->id;
- }
- }
- return \@return;
-}
-
-# my $in = OpenILS::Application::Acq::EDI->process_retrieval($file_content, $remote_filename, $server, $account_id, $editor);
-
-sub process_retrieval {
- my $incoming = Fieldmapper::acq::edi_message->new;
- my ($class, $content, $remote, $server, $account_or_id, $e) = @_;
- $content or return;
- $e ||= new_editor;
-
- my $account = __PACKAGE__->record_activity( $account_or_id, $e );
-
- my $z; # must predeclare
- $z = ( $content =~ s/('UNH\+\d+\+ORDRSP:)0(:96A:UN')/$1D$2/g )
- and $logger->warn("Patching bogus spec reference ORDRSP:0:96A:UN => ORDRSP:D:96A:UN ($z times)"); # Hack/fix some faulty "0" in (B&T) data
-
- $incoming->remote_file($remote);
- $incoming->account($account->id);
- $incoming->edi($content);
- $incoming->message_type(($content =~ /'UNH\+\d+\+(\S{6}):/) ? $1 : 'ORDRSP'); # cheap sniffing, ORDRSP fallback
- __PACKAGE__->attempt_translation($incoming);
- $e->xact_begin;
- $e->create_acq_edi_message($incoming);
- $e->xact_commit;
- # refresh: send process_jedi the updated row
- $e->xact_begin;
- my $outgoing = $e->retrieve_acq_edi_message($incoming->id); # refresh again!
- $e->xact_rollback;
- my $res = __PACKAGE__->process_jedi($outgoing, $server, $account, $e);
- $e->xact_begin;
- $outgoing = $e->retrieve_acq_edi_message($incoming->id); # refresh again!
- $e->xact_rollback;
- $outgoing->status($res ? 'processed' : 'proc_error');
- if ($res) {
- $e->xact_begin;
- $e->update_acq_edi_message($outgoing);
- $e->xact_commit;
- }
- return $outgoing;
-}
-
-# ->send_core
-# $account is a Fieldmapper object for acq.edi_account row
-# $messageset is an arrayref with acq.edi_message.id values
-# $e is optional editor object
-sub send_core {
- my ($class, $account, $message_ids, $e) = @_; # $e is a working editor
-
- ($account and scalar @$message_ids) or return;
- $e ||= new_editor();
-
- $e->xact_begin;
- my @messageset = map {$e->retrieve_acq_edi_message($_)} @$message_ids;
- $e->xact_rollback;
- my $m_count = scalar(@messageset);
- (scalar(@$message_ids) == $m_count) or
- $logger->warn(scalar(@$message_ids) - $m_count . " bad IDs passed to send_core (ignored)");
-
- my $log_str = sprintf "EDI send to edi_account %s (%s)", $account->id, $account->host;
- $logger->info("$log_str: $m_count message(s)");
- $m_count or return;
-
- my $server;
- my $server_error;
- unless ($server = __PACKAGE__->remote_account($account, 1)) { # assignment, not comparison
- $logger->error("Failed remote account connection for $log_str");
- $server_error = 1;
- };
- foreach (@messageset) {
- $_ or next; # we already warned about bum ids
- my ($res, $error);
- if ($server_error) {
- $error = "Server error: Failed remote account connection for $log_str"; # already told $logger, this is to update object below
- } elsif (! $_->edi) {
- $logger->error("Message (id " . $_->id. ") for $log_str has no EDI content");
- $error = "EDI empty!";
- } elsif ($res = $server->put({remote_path => $account->path, content => $_->edi, single_ext => 1})) {
- # This is the successful case!
- $_->remote_file($res);
- $_->status('complete');
- $_->process_time('NOW'); # For outbound files, sending is the end of processing on the EG side.
- $logger->info("Sent message (id " . $_->id. ") via $log_str");
- } else {
- $logger->error("(S)FTP put to $log_str FAILED: " . ($server->error || 'UNKOWNN'));
- $error = "put FAILED: " . ($server->error || 'UNKOWNN');
- }
- if ($error) {
- $_->error($error);
- $_->error_time('NOW');
- }
- $logger->info("Calling update_acq_edi_message");
- $e->xact_begin;
- unless ($e->update_acq_edi_message($_)) {
- $logger->error("EDI send_core update_acq_edi_message failed for message object: " . Dumper($_));
- OpenILS::Application::Acq::EDI::Translator->debug_file(Dumper($_ ), '/tmp/update_acq_edi_message.FAIL');
- OpenILS::Application::Acq::EDI::Translator->debug_file(Dumper($_->to_bare_hash), '/tmp/update_acq_edi_message.FAIL.to_bare_hash');
- }
- # There's always an update, even if we failed.
- $e->xact_commit;
- __PACKAGE__->record_activity($account, $e); # There's always an update, even if we failed.
- }
- return \@messageset;
-}
-
-# attempt_translation does not touch the DB, just the object.
-sub attempt_translation {
- my ($class, $edi_message, $to_edi) = @_;
- my $tran = translator();
- my $ret = $to_edi ? $tran->json2edi($edi_message->jedi) : $tran->edi2json($edi_message->edi);
-# $logger->error("json: " . Dumper($json)); # debugging
- if (not $ret or (! ref($ret)) or $ret->is_fault) { # RPC::XML::fault on failure
- $edi_message->status('trans_error');
- $edi_message->error_time('NOW');
- my $pre = "EDI Translator " . ($to_edi ? 'json2edi' : 'edi2json') . " failed";
- my $message = ref($ret) ?
- ("$pre, Error " . $ret->code . ": " . __PACKAGE__->nice_string($ret->string)) :
- ("$pre: " . __PACKAGE__->nice_string($ret) ) ;
- $edi_message->error($message);
- $logger->error( $message);
- return;
- }
- $edi_message->status('translated');
- $edi_message->translate_time('NOW');
- if ($to_edi) {
- $edi_message->edi($ret->value); # translator returns an object
- } else {
- $edi_message->jedi($ret->value); # translator returns an object
- }
- return $edi_message;
-}
-
-sub retrieve_vendors {
- my ($self, $e, $vendor_id, $last_activity) = @_; # $e is a working editor
-
- $e ||= new_editor();
-
- my $criteria = {'+acqpro' => {active => 't'}};
- $criteria->{'+acqpro'}->{id} = $vendor_id if $vendor_id;
- return $e->search_acq_edi_account([
- $criteria, {
- 'join' => 'acqpro',
- flesh => 1,
- flesh_fields => {
- acqedi => ['provider']
- }
- }
- ]);
-# {"id":{"!=":null},"+acqpro":{"active":"t"}}, {"join":"acqpro", "flesh_fields":{"acqedi":["provider"]},"flesh":1}
-}
-
-# This is the SRF-exposed call, so it does checkauth
-
-sub retrieve {
- my ($self, $conn, $auth, $vendor_id, $last_activity, $max) = @_;
-
- my $e = new_editor(authtoken=>$auth);
- unless ($e and $e->checkauth()) {
- $logger->warn("checkauth failed for authtoken '$auth'");
- return ();
- }
- # return $e->die_event unless $e->allowed('RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency); # add permission here ?
-
- my $set = __PACKAGE__->retrieve_vendors($e, $vendor_id, $last_activity) or return $e->die_event;
- return __PACKAGE__->retrieve_core($e, $set, $max);
-}
-
-
-# field_map takes the hashref of vendor data with fields from acq.edi_account and
-# maps them to the argument style needed for RemoteAccount. It also extrapolates
-# data from the remote_host string for type and port, when available.
-
-sub field_map {
- my $self = shift;
- my $vendor = shift or return;
- my $no_override = @_ ? shift : 0;
- my %args = ();
- $verbose and $logger->warn("vendor: " . Dumper($vendor));
- foreach (keys %map) {
- $args{$map{$_}} = $vendor->$_ if defined $vendor->$_;
- }
- unless ($no_override) {
- $args{remote_path} = $vendor->in_dir; # override "path" with "in_dir"
- }
- my $host = $args{remote_host} || '';
- ($host =~ s/^(S?FTP)://i and $args{type} = uc($1)) or
- ($host =~ s/^(SSH|SCP)://i and $args{type} = 'SCP' ) ;
- $host =~ s/:(\d+)$// and $args{port} = $1;
- ($args{remote_host} = $host) =~ s#/+##;
- $verbose and $logger->warn("field_map: " . Dumper(\%args));
- return %args;
-}
-
-
-# The point of remote_account is to get the RemoteAccount object with args from the DB
-
-sub remote_account {
- my ($self, $vendor, $outbound, $e) = @_;
-
- unless (ref($vendor)) { # It's not a hashref/object.
- $vendor or return; # If in fact it's nothing: abort!
- # else it's a vendor_id string, so get the full vendor data
- $e ||= new_editor();
- my $set_of_one = $self->retrieve_vendors($e, $vendor) or return;
- $vendor = shift @$set_of_one;
- }
-
- return OpenILS::Utils::RemoteAccount->new(
- $self->field_map($vendor, $outbound)
- );
-}
-
-# takes account ID or account Fieldmapper object
-
-sub record_activity {
- my ($class, $account_or_id, $e) = @_;
- $account_or_id or return;
- $e ||= new_editor();
- my $account = ref($account_or_id) ? $account_or_id : $e->retrieve_acq_edi_account($account_or_id);
- $logger->info("EDI record_activity calling update_acq_edi_account");
- $account->last_activity('NOW') or return;
- $e->xact_begin;
- $e->update_acq_edi_account($account) or $logger->warn("EDI: in record_activity, update_acq_edi_account FAILED");
- $e->xact_commit;
- return $account;
-}
-
-sub nice_string {
- my $class = shift;
- my $string = shift or return '';
- chomp($string);
- my $head = @_ ? shift : 100;
- my $tail = @_ ? shift : 25;
- (length($string) < $head + $tail) and return $string;
- my $h = substr($string,0,$head);
- my $t = substr($string, -1*$tail);
- $h =~s/\s*$//o;
- $t =~s/\s*$//o;
- return "$h ... $t";
- # return substr($string,0,$head) . "... " . substr($string, -1*$tail);
-}
-
-sub jedi2perl {
- my ($class, $jedi) = @_;
- $jedi or return;
- my $msg = OpenSRF::Utils::JSON->JSON2perl( $jedi );
- open (FOO, ">>/tmp/JSON2perl_dump.txt");
- print FOO Dumper($msg), "\n\n";
- close FOO;
- $logger->warn("Dumped JSON2perl to /tmp/JSON2perl_dump.txt");
- return $msg;
-}
-
-our @datecodes = (35, 359, 17, 191, 69, 76, 75, 79, 85, 74, 84, 223);
-our @noop_6063 = (21);
-
-# ->process_jedi($message, $server, $remote, $e)
-# $message is an edi_message object
-#
-sub process_jedi {
- my ($class, $message, $server, $remote, $e) = @_;
- $message or return;
- $server ||= {}; # context
- $remote ||= {}; # context
- $e ||= new_editor;
- my $jedi;
- unless (ref($message) and $jedi = $message->jedi) { # assignment, not comparison
- $logger->warn("EDI process_jedi missing required argument (edi_message object with jedi)!");
- return;
- }
- my $perl = __PACKAGE__->jedi2perl($jedi);
- my $error = '';
- if (ref($message) and not $perl) {
- $error = ($message->error || '') . " JSON2perl (jedi2perl) FAILED to convert jedi";
- }
- elsif (! $perl->{body}) {
- $error = "EDI interchange body not found!";
- }
- elsif (! $perl->{body}->[0]) {
- $error = "EDI interchange body not a populated arrayref!";
- }
- if ($error) {
- $logger->warn($error);
- $message->error($error);
- $message->error_time('NOW');
- $e->xact_begin;
- $e->update_acq_edi_message($message) or $logger->warn("EDI update_acq_edi_message failed! $!");
- $e->xact_commit;
- return;
- }
-
-# Crazy data structure. Most of the arrays will be 1 element... we think.
-# JEDI looks like:
-# {'body' => [{'ORDERS' => [['UNH',{'0062' => '4635','S009' => {'0057' => 'EAN008','0051' => 'UN','0052' => 'D','0065' => 'ORDERS', ...
-#
-# So you might access it like:
-# $obj->{body}->[0]->{ORDERS}->[0]->[0] eq 'UNH'
-
- $logger->info("EDI interchange body has " . scalar(@{$perl->{body}}) . " message(s)");
- my @ok_msg_codes = qw/ORDRSP OSTRPT/;
- my @messages;
- my $i = 0;
- foreach my $part (@{$perl->{body}}) {
- $i++;
- unless (ref $part and scalar keys %$part) {
- $logger->warn("EDI interchange message $i lacks structure. Skipping it.");
- next;
- }
- foreach my $key (keys %$part) {
- if (! grep {$_ eq $key} @ok_msg_codes) { # We only do one type for now. TODO: other types here
- $logger->warn("EDI interchange $i contains unhandled '$key' message. Ignoring it.");
- next;
- }
- my $msg = __PACKAGE__->message_object($part->{$key}) or next;
- push @messages, $msg;
-
- my $bgm = $msg->xpath('BGM') or $logger->warn("EDI No BGM segment found?!");
- my $tag4343 = $msg->xpath('BGM/4343');
- my $tag1225 = $msg->xpath('BGM/1225');
- if (ref $tag4343) {
- $logger->info(sprintf "EDI $key BGM/4343 Response Type: %s - %s", $tag4343->value, $tag4343->label)
- } else {
- $logger->warn("EDI $key BGM/4343 Response Type Code unrecognized"); # next; #?
- }
- if (ref $tag1225) {
- $logger->info(sprintf "EDI $key BGM/1225 Message Function: %s - %s", $tag1225->value, $tag1225->label);
- } else {
- $logger->warn("EDI $key BGM/1225 Message Function Code unrecognized"); # next; #?
- }
-
- # TODO: currency check, just to be paranoid
- # *should* be unnecessary (vendor should reply in currency we send in ORDERS)
- # That begs a policy question: how to handle mismatch? convert (bad accuracy), reject, or ignore? I say ignore.
-
- # ALL those codes below are basically some form of (lastest) delivery date/time
- # see, e.g.: http://www.stylusstudio.com/edifact/D04B/2005.htm
- # The order is the order of definitiveness (first match wins)
- # Note: if/when we do serials via EDI, dates (and ranges/periods) will need massive special handling
- my @dates;
- my $ddate;
-
- foreach my $date ($msg->xpath('delivery_schedule')) {
- my $val_2005 = $date->xpath_value('DTM/2005') or next;
- (grep {$val_2005 eq $_} @datecodes) or next; # no match means some other kind of date we don't care about
- push @dates, $date;
- }
- if (@dates) {
- DATECODE: foreach my $dcode (@datecodes) { # now cycle back through hits in order of dcode definitiveness
- foreach my $date (@dates) {
- $date->xpath_value('DTM/2005') == $dcode or next;
- $ddate = $date->xpath_value('DTM/2380') and last DATECODE;
- # TODO: conversion based on format specified in DTM/2379 (best encapsulated in Business::EDI)
- }
- }
- }
- foreach my $detail ($msg->part('line_detail')) {
- my $eg_line = __PACKAGE__->eg_li($detail, $remote, $server->{remote_host}, $e) or next;
- my $li_date = $detail->xpath_value('DTM/2380') || $ddate;
- my $price = $detail->xpath_value('line_price/PRI/5118') || '';
- $eg_line->expected_recv_time($li_date) if $li_date;
- $eg_line->estimated_unit_price($price) if $price;
- if (not $message->purchase_order) { # first good lineitem sets the message PO link
- $message->purchase_order($eg_line->purchase_order); # EG $message object NOT Business::EDI $msg object
- $e->xact_begin;
- $e->update_acq_edi_message($message) or $logger->warn("EDI update_acq_edi_message (for PO number) failed! $!");
- $e->xact_commit;
- }
- # $e->search_acq_edi_account([]);
- my $touches = 0;
- my $eg_lids = $e->search_acq_lineitem_detail({lineitem => $eg_line->id}); # should be the same as $eg_line->lineitem_details
- my $lidcount = scalar(@$eg_lids);
- $lidcount == $eg_line->item_count or $logger->warn(
- sprintf "EDI: LI %s itemcount (%d) mismatch, %d LIDs found", $eg_line->id, $eg_line->item_count, $lidcount
- );
- foreach my $qty ($detail->part('all_QTY')) {
- my $ubound = $qty->xpath_value('6060') or next; # nothing to do if qty is 0
- my $val_6063 = $qty->xpath_value('6063');
- $ubound > 0 or next; # don't be crazy!
- if (! $val_6063) {
- $logger->warn("EDI: Response for LI " . $eg_line->id . " specifies quantity $ubound with no 6063 code! Contact vendor to resolve.");
- next;
- }
-
- my $eg_reason = $e->retrieve_acq_cancel_reason(1200 + $val_6063); # DB populated w/ 6063 keys in 1200's
- if (! $eg_reason) {
- $logger->warn("EDI: Unhandled quantity code '$val_6063' (LI " . $eg_line->id . ") $ubound items unprocessed");
- next;
- } elsif (grep {$val_6063 == $_} @noop_6063) { # an FYI like "ordered quantity"
- $ubound eq $lidcount
- or $logger->warn("EDI: LI " . $eg_line->id . " -- Vendor says we ordered $ubound, but we have $lidcount LIDs!)");
- next;
- }
- # elsif ($val_6063 == 83) { # backorder
- #} elsif ($val_6063 == 85) { # cancel
- #} elsif ($val_6063 == 12 or $val_6063 == 57 or $val_6063 == 84 or $val_6063 == 118) {
- # despatched, in transit, urgent delivery, or quantity manifested
- #}
- if ($touches >= $lidcount) {
- $logger->warn("EDI: LI " . $eg_line->id . ", We already updated $touches of $lidcount LIDS, " .
- "but message wants QTY $ubound more set to " . $eg_reason->label . ". Ignoring!");
- next;
- }
- $e->xact_begin;
- foreach (1 .. $ubound) {
- my $eg_lid = shift @$eg_lids or $logger->warn("EDI: Used up all $lidcount LIDs! Ignoring extra status " . $eg_reason->label);
- $eg_lid or next;
- $logger->debug(sprintf "Updating LID %s to %s", $eg_lid->id, $eg_reason->label);
- $eg_lid->cancel_reason($eg_reason->id);
- $e->update_acq_lineitem_detail($eg_lid);
- $touches++;
- }
- $e->xact_commit;
- if ($ubound == $eg_line->item_count) {
- $eg_line->cancel_reason($eg_reason->id); # if ALL the items have the same cancel_reason, the PO gets it too
- }
- }
- $eg_line->edit_time('NOW'); # TODO: have this field automatically updated via ON UPDATE trigger.
- $e->xact_begin;
- $e->update_acq_lineitem($eg_line) or $logger->warn("EDI: update_acq_lineitem FAILED");
- $e->xact_commit;
- # print STDERR "Lineitem update: ", Dumper($eg_line);
- }
- }
- }
- return \@messages;
-}
-
-# returns message object if processing should continue
-# returns false/undef value if processing should abort
-
-sub message_object {
- my $class = shift;
- my $body = shift or return;
- my $key = shift if @_;
- my $keystring = $key || 'UNSPECIFIED';
-
- my $msg = Business::EDI::Message->new($body);
- unless ($msg) {
- $logger->error("EDI interchange message: $keystring body failed Business::EDI constructor. Skipping it.");
- return;
- }
- $key = $msg->code if ! $key; # Now we set the key for reference if it wasn't specified
- my $val_0065 = $msg->xpath_value('UNH/S009/0065') || '';
- unless ($val_0065 eq $key) {
- $logger->error("EDI $key UNH/S009/0065 ('$val_0065') conflicts w/ message type $key. Aborting");
- return;
- }
- my $val_0051 = $msg->xpath_value('UNH/S009/0051') || '';
- unless ($val_0051 eq 'UN') {
- $logger->warn("EDI $key UNH/S009/0051 designates '$val_0051', not 'UN' as controlling agency. Attempting to process anyway");
- }
- my $val_0054 = $msg->xpath_value('UNH/S009/0054') || '';
- if ($val_0054) {
- $logger->info("EDI $key UNH/S009/0054 uses Spec revision version '$val_0054'");
- # Possible Spec Version limitation
- # my $yy = $tag_0054 ? substr($val_0054,0,2) : '';
- # unless ($yy eq '00' or $yy > 94 ...) {
- # $logger->warn("EDI $key UNH/S009/0051 Spec revision version '$val_0054' not supported");
- # }
- } else {
- $logger->warn("EDI $key UNH/S009/0054 does not reference a known Spec revision version");
- }
- return $msg;
-}
-
-=head2 ->eg_li($lineitem_object, [$remote, $server_log_string, $editor])
-
-my $line_item = OpenILS::Application::Acq::EDI->eg_li($edi_line, $remote, "test_server_01", $e);
-
- $remote is a acq.edi_account Fieldmapper object.
- $server_log_string is an arbitrary string use to identify the remote host in potential log messages.
-
-Updates:
- acq.lineitem.estimated_unit_price,
- acq.lineitem.state (dependent on mapping codes),
- acq.lineitem.expected_recv_time,
- acq.lineitem.edit_time (consequently)
-
-=cut
-
-sub eg_li {
- my ($class, $line, $server, $server_log_string, $e) = @_;
- $line or return;
- $e ||= new_editor();
-
- my $id;
- # my $rff = $line->part('line_reference/RFF') or $logger->warn("EDI ORDRSP line_detail/RFF missing!");
- my $val_1153 = $line->xpath_value('line_reference/RFF/1153') || '';
- my $val_1154 = $line->xpath_value('line_reference/RFF/1154') || '';
- my $val_1082 = $line->xpath_value('LIN/1082') || '';
-
- my @po_nums;
-
- $val_1154 =~ s#^(.*)\/##; # Many sources send the ID as 'order_ID/LI_ID'
- $1 and push @po_nums, $1;
- $val_1082 =~ s#^(.*)\/##; # Many sources send the ID as 'order_ID/LI_ID'
- $1 and push @po_nums, $1;
-
- # TODO: possible check of po_nums
- # now do a lot of checking
-
- if ($val_1153 eq 'LI') {
- $id = $val_1154 or $logger->warn("EDI ORDRSP RFF/1154 reference to LI empty. Attempting failover to LIN/1082");
- } else {
- $logger->warn("EDI ORDRSP RFF/1153 unexpected value ('$val_1153', not 'LI'). Attempting failover to LIN/1082");
- }
-
- # FIXME - the line item ID in LIN/1082 ought to match RFF/1154, but
- # not all materials vendors obey this. Commenting out check for now
- # as being too strict.
- #if ($id and $val_1082 and $val_1082 ne $id) {
- # $logger->warn("EDI ORDRSP LIN/1082 Line Item ID mismatch ($id vs. $val_1082): cannot target update");
- # return;
- #}
-
- $id ||= $val_1082 || '';
- if ($id eq '') {
- $logger->warn('Cannot identify line item from EDI message');
- return;
- }
-
- $logger->info("EDI retrieve/update lineitem $id");
-
- my $li = OpenILS::Application::Acq::Lineitem::retrieve_lineitem_impl($e, $id, {
- flesh_li_details => 1,
- }, 1); # Could send more {options}. The 1 is for no_auth.
-
- if (! $li or ref($li) ne 'Fieldmapper::acq::lineitem') {
- $logger->error("EDI failed to retrieve lineitem by id '$id' for server $server_log_string");
- return;
- }
- unless ((! $server) or (! $server->provider)) { # but here we want $server to be acq.edi_account instead of RemoteAccount
- if ($server->provider != $li->provider) {
- # links go both ways: acq.provider.edi_default and acq.edi_account.provider
- $logger->info("EDI acct provider (" . $server->provider. ") doesn't match lineitem provider("
- . $li->provider . "). Checking acq.provider.edi_default...");
- my $provider = $e->retrieve_acq_provider($li->provider);
- if ($provider->edi_default != $server->id) {
- $logger->error(sprintf "EDI provider/acct %s/%s (%s) is blocked from updating lineitem $id belonging to provider/edi_default %s/%s",
- $server->provider, $server->id, $server->label, $li->provider, $provider->edi_default);
- return;
- }
- }
- }
-
- my @lin_1229 = $line->xpath('LIN/1229') or $logger->warn("EDI LIN/1229 Action Code missing!");
- my $key = $lin_1229[0] or return;
-
- my $eg_reason = $e->retrieve_acq_cancel_reason(1000 + $key->value); # DB populated w/ spec keys in 1000's
- $eg_reason or $logger->warn(sprintf "EDI LIN/1229 Action Code '%s' (%s) not recognized in acq.cancel_reason", $key->value, $key->label);
- $eg_reason or return;
-
- $li->cancel_reason($eg_reason->id);
- unless ($eg_reason->keep_debits) {
- $logger->warn("EDI LIN/1229 Action Code '%s' (%s) has keep_debits=0", $key->value, $key->label);
- }
-
- my @prices = $line->xpath_value("line_price/PRI/5118");
- $li->estimated_unit_price($prices[0]) if @prices;
-
- return $li;
-}
-
-# caching not needed for now (edi_fetcher is asynchronous)
-# sub get_reason {
-# my ($class, $key, $e) = @_;
-# $reasons->{$key} and return $reasons->{$key};
-# $e ||= new_editor();
-# $reasons->{$key} = $e->retrieve_acq_cancel_reason($key);
-# return $reasons->{$key};
-# }
-
-1;
-
-__END__
-
-Example JSON data.
-
-Note the pseudo-hash 2-element arrays.
-
-[
- 'SG26',
- [
- [
- 'LIN',
- {
- '1229' => '5',
- '1082' => 1,
- 'C212' => {
- '7140' => '9780446360272',
- '7143' => 'EN'
- }
- }
- ],
- [
- 'IMD',
- {
- '7081' => 'BST',
- '7077' => 'F',
- 'C273' => {
- '7008' => [
- 'NOT APPLIC WEBSTERS NEW WORLD THESA'
- ]
- }
- }
- ],
- [
- 'QTY',
- {
- 'C186' => {
- '6063' => '21',
- '6060' => 10
- }
- }
- ],
- [
- 'QTY',
- {
- 'C186' => {
- '6063' => '12',
- '6060' => 10
- }
- }
- ],
- [
- 'QTY',
- {
- 'C186' => {
- '6063' => '85',
- '6060' => 0
- }
- }
- ],
- [
- 'FTX',
- {
- '4451' => 'LIN',
- 'C107' => {
- '4441' => '01',
- '3055' => '28',
- '1131' => '8B'
- }
- }
- ],
- [
- 'SG30',
- [
- [
- 'PRI',
- {
- 'C509' => {
- '5118' => '4.5',
- '5387' => 'SRP',
- '5125' => 'AAB'
- }
- }
- ]
- ]
- ],
- [
- 'SG31',
- [
- [
- 'RFF',
- {
- 'C506' => {
- '1154' => '8/1',
- '1153' => 'LI'
- }
- }
- ]
- ]
- ]
- ]
-],
-
+++ /dev/null
-package OpenILS::Application::Acq::EDI::Translator;
-
-use warnings;
-use strict;
-
-use RPC::XML::Client;
-use Data::Dumper;
-
-# DEFAULTS
-my $proto = 'http://';
-my $host = $proto . 'localhost';
-my $path = '/EDI';
-my $port = 9191;
-my $verbose = 0;
-
-sub new {
- my ($class, %args) = @_;
- my $self = bless(\%args, $class);
- $self->init;
- return $self;
-}
-
-sub init {
- my $self = shift;
- $self->host_cleanup;
-}
-
-sub host_cleanup {
- my $self = shift;
- my $target = $self->{host} || $host;
- $target =~ /^\S+:\/\// or $target = ($self->{proto} || $proto) . $target;
- $target =~ /:\d+$/ or $target .= ':' . ($self->{port} || $port);
- $target .= ($self->{path} || $path);
- $self->{verbose} and print "Cleanup: $self->{host} ==> $target\n";
- $self->{host} = $target;
- return $target;
-}
-
-sub client {
- my $self = shift;
- return $self->{client} ||= RPC::XML::Client->new($self->{host}); # TODO: auth
-}
-
-sub debug_file {
- my $self = shift;
- my $text = shift;
- my $filename = @_ ? shift : ('/tmp/' . __PACKAGE__ . '_unknown.tmp');
- unless (open (TMP_EDI, ">$filename")) {
- warn "Cannot write $filename: $!";
- return;
- }
- print TMP_EDI $text, "\n";
- close TMP_EDI;
- return 1;
-}
-
-sub json2edi {
- my $self = shift;
- my $text = shift;
- $self->debug_file($text, '/tmp/perl_json2edi.tmp');
- my $client = $self->client();
- $self->{verbose} and print "Trying json2edi on host: $self->{host}\n";
- $client->request->header('Content-Type' => 'text/xml;charset=utf-8');
- my $resp = $client->send_request('json2edi', $text);
- $self->{verbose} and print Dumper($resp);
- return $resp;
-}
-
-sub edi2json {
- my $self = shift;
- my $text = shift;
- $self->debug_file($text, '/tmp/perl_edi2json.tmp');
- my $client = $self->client();
- $self->{verbose} and print "Trying edi2json on host: $self->{host}\n";
- $client->request->header('Content-Type' => 'text/xml;charset=utf-8');
- my $resp = $client->send_request('edi2json', $text);
- $self->{verbose} and print Dumper($resp);
- return $resp;
-}
-
-1;
-
+++ /dev/null
-package OpenILS::Application::Acq::Financials;
-use base qw/OpenILS::Application/;
-use strict; use warnings;
-
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenILS::Const qw/:const/;
-use OpenSRF::Utils::SettingsClient;
-use OpenILS::Event;
-use OpenILS::Application::AppUtils;
-use OpenILS::Application::Acq::Lineitem;
-my $U = 'OpenILS::Application::AppUtils';
-
-# ----------------------------------------------------------------------------
-# Funding Sources
-# ----------------------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'create_funding_source',
- api_name => 'open-ils.acq.funding_source.create',
- signature => {
- desc => 'Creates a new funding_source',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'funding source object to create', type => 'object'}
- ],
- return => {desc => 'The ID of the new funding_source'}
- }
-);
-
-sub create_funding_source {
- my($self, $conn, $auth, $funding_source) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- return $e->die_event unless $e->allowed('ADMIN_FUNDING_SOURCE', $funding_source->owner);
- $e->create_acq_funding_source($funding_source) or return $e->die_event;
- $e->commit;
- return $funding_source->id;
-}
-
-
-__PACKAGE__->register_method(
- method => 'delete_funding_source',
- api_name => 'open-ils.acq.funding_source.delete',
- signature => {
- desc => 'Deletes a funding_source',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'funding source ID', type => 'number'}
- ],
- return => {desc => '1 on success, Event on failure'}
- }
-);
-
-sub delete_funding_source {
- my($self, $conn, $auth, $funding_source_id) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- my $funding_source = $e->retrieve_acq_funding_source($funding_source_id) or return $e->die_event;
- return $e->die_event unless $e->allowed('ADMIN_FUNDING_SOURCE', $funding_source->owner, $funding_source);
- $e->delete_acq_funding_source($funding_source) or return $e->die_event;
- $e->commit;
- return 1;
-}
-
-__PACKAGE__->register_method(
- method => 'retrieve_funding_source',
- api_name => 'open-ils.acq.funding_source.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Retrieves a new funding_source',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'funding source ID', type => 'number'}
- ],
- return => {desc => 'The funding_source object on success, Event on failure'}
- }
-);
-
-sub retrieve_funding_source {
- my($self, $conn, $auth, $funding_source_id, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- $options ||= {};
-
- my $flesh = {flesh => 1, flesh_fields => {acqfs => []}};
- push(@{$flesh->{flesh_fields}->{acqfs}}, 'credits') if $$options{flesh_credits};
- push(@{$flesh->{flesh_fields}->{acqfs}}, 'allocations') if $$options{flesh_allocations};
-
- my $funding_source = $e->retrieve_acq_funding_source([$funding_source_id, $flesh]) or return $e->event;
-
- return $e->event unless $e->allowed(
- ['ADMIN_FUNDING_SOURCE','MANAGE_FUNDING_SOURCE', 'VIEW_FUNDING_SOURCE'],
- $funding_source->owner, $funding_source);
-
- $funding_source->summary(retrieve_funding_source_summary_impl($e, $funding_source))
- if $$options{flesh_summary};
- return $funding_source;
-}
-
-__PACKAGE__->register_method(
- method => 'retrieve_org_funding_sources',
- api_name => 'open-ils.acq.funding_source.org.retrieve',
- stream => 1,
- signature => {
- desc => 'Retrieves all the funding_sources associated with an org unit that the requestor has access to see',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'List of org Unit IDs. If no IDs are provided, this method returns the
- full set of funding sources this user has permission to view', type => 'number'},
- {desc => q/Limiting permission. this permission is used find the work-org tree from which
- the list of orgs is generated if no org ids are provided.
- The default is ADMIN_FUNDING_SOURCE/, type => 'string'},
- ],
- return => {desc => 'The funding_source objects on success, empty array otherwise'}
- }
-);
-
-sub retrieve_org_funding_sources {
- my($self, $conn, $auth, $org_id_list, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- $options ||= {};
-
- my $limit_perm = ($$options{limit_perm}) ? $$options{limit_perm} : 'ADMIN_FUNDING_SOURCE';
- return OpenILS::Event->new('BAD_PARAMS')
- unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_FUNDING_SOURCE/;
-
- my $org_ids = ($org_id_list and @$org_id_list) ? $org_id_list :
- $U->user_has_work_perm_at($e, $limit_perm, {descendants =>1});
-
- return [] unless @$org_ids;
- my $sources = $e->search_acq_funding_source({owner => $org_ids});
-
- for my $source (@$sources) {
- $source->summary(retrieve_funding_source_summary_impl($e, $source))
- if $$options{flesh_summary};
- $conn->respond($source);
- }
-
- return undef;
-}
-
-sub retrieve_funding_source_summary_impl {
- my($e, $source) = @_;
- my $at = $e->search_acq_funding_source_allocation_total({funding_source => $source->id})->[0];
- my $b = $e->search_acq_funding_source_balance({funding_source => $source->id})->[0];
- my $ct = $e->search_acq_funding_source_credit_total({funding_source => $source->id})->[0];
- return {
- allocation_total => ($at) ? $at->amount : 0,
- balance => ($b) ? $b->amount : 0,
- credit_total => ($ct) ? $ct->amount : 0,
- };
-}
-
-
-__PACKAGE__->register_method(
- method => 'create_funding_source_credit',
- api_name => 'open-ils.acq.funding_source_credit.create',
- signature => {
- desc => 'Create a new funding source credit',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'funding source credit object', type => 'object'}
- ],
- return => {desc => 'The ID of the new funding source credit on success, Event on failure'}
- }
-);
-
-sub create_funding_source_credit {
- my($self, $conn, $auth, $fs_credit) = @_;
- my $e = new_editor(authtoken=>$auth, xact=>1);
- return $e->event unless $e->checkauth;
-
- my $fs = $e->retrieve_acq_funding_source($fs_credit->funding_source)
- or return $e->die_event;
- return $e->die_event unless $e->allowed(['MANAGE_FUNDING_SOURCE'], $fs->owner, $fs);
-
- $e->create_acq_funding_source_credit($fs_credit) or return $e->die_event;
- $e->commit;
- return $fs_credit->id;
-}
-
-
-# ---------------------------------------------------------------
-# funds
-# ---------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'create_fund',
- api_name => 'open-ils.acq.fund.create',
- signature => {
- desc => 'Creates a new fund',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund object to create', type => 'object'}
- ],
- return => {desc => 'The ID of the newly created fund object'}
- }
-);
-
-sub create_fund {
- my($self, $conn, $auth, $fund) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- return $e->die_event unless $e->allowed('ADMIN_FUND', $fund->org);
- $e->create_acq_fund($fund) or return $e->die_event;
- $e->commit;
- return $fund->id;
-}
-
-
-__PACKAGE__->register_method(
- method => 'delete_fund',
- api_name => 'open-ils.acq.fund.delete',
- signature => {
- desc => 'Deletes a fund',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund ID', type => 'number'}
- ],
- return => {desc => '1 on success, Event on failure'}
- }
-);
-
-sub delete_fund {
- my($self, $conn, $auth, $fund_id) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- my $fund = $e->retrieve_acq_fund($fund_id) or return $e->die_event;
- return $e->die_event unless $e->allowed('ADMIN_FUND', $fund->org, $fund);
- $e->delete_acq_fund($fund) or return $e->die_event;
- $e->commit;
- return 1;
-}
-
-__PACKAGE__->register_method(
- method => 'retrieve_fund',
- api_name => 'open-ils.acq.fund.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Retrieves a new fund',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund ID', type => 'number'}
- ],
- return => {desc => 'The fund object on success, Event on failure'}
- }
-);
-
-sub retrieve_fund {
- my($self, $conn, $auth, $fund_id, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- $options ||= {};
-
- my $flesh = {flesh => 2, flesh_fields => {acqf => []}};
- if ($options->{"flesh_tags"}) {
- push @{$flesh->{"flesh_fields"}->{"acqf"}}, "tags";
- $flesh->{"flesh_fields"}->{"acqftm"} = ["tag"];
- }
- push(@{$flesh->{flesh_fields}->{acqf}}, 'debits') if $$options{flesh_debits};
- push(@{$flesh->{flesh_fields}->{acqf}}, 'allocations') if $$options{flesh_allocations};
- push(@{$flesh->{flesh_fields}->{acqfa}}, 'funding_source') if $$options{flesh_allocation_sources};
-
- my $fund = $e->retrieve_acq_fund([$fund_id, $flesh]) or return $e->event;
- return $e->event unless $e->allowed(['ADMIN_FUND','MANAGE_FUND', 'VIEW_FUND'], $fund->org, $fund);
- $fund->summary(retrieve_fund_summary_impl($e, $fund))
- if $$options{flesh_summary};
- return $fund;
-}
-
-__PACKAGE__->register_method(
- method => 'retrieve_org_funds',
- api_name => 'open-ils.acq.fund.org.retrieve',
- stream => 1,
- signature => {
- desc => 'Retrieves all the funds associated with an org unit',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'List of org Unit IDs. If no IDs are provided, this method returns the
- full set of funding sources this user has permission to view', type => 'number'},
- {desc => q/Options hash.
- "limit_perm" -- this permission is used find the work-org tree from which
- the list of orgs is generated if no org ids are provided. The default is ADMIN_FUND.
- "flesh_summary" -- if true, the summary field on each fund is fleshed
- The default is ADMIN_FUND/, type => 'string'},
- ],
- return => {desc => 'The fund objects on success, Event on failure'}
- }
-);
-
-__PACKAGE__->register_method(
- method => 'retrieve_org_funds',
- api_name => 'open-ils.acq.fund.org.years.retrieve');
-
-
-sub retrieve_org_funds {
- my($self, $conn, $auth, $filter, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- $filter ||= {};
- $options ||= {};
-
- my $limit_perm = ($$options{limit_perm}) ? $$options{limit_perm} : 'ADMIN_FUND';
- return OpenILS::Event->new('BAD_PARAMS')
- unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_FUND/;
-
- $filter->{org} = $filter->{org} ||
- $U->user_has_work_perm_at($e, $limit_perm, {descendants =>1});
- return undef unless @{$filter->{org}};
-
- my $query = [
- $filter,
- {
- limit => $$options{limit} || 50,
- offset => $$options{offset} || 0,
- order_by => $$options{order_by} || {acqf => 'name'}
- }
- ];
-
- if($self->api_name =~ /years/) {
- # return the distinct set of fund years covered by the selected funds
- my $data = $e->json_query({
- select => {
- acqf => [{column => 'year', transform => 'distinct'}]
- },
- from => 'acqf',
- where => $filter}
- );
-
- return [map { $_->{year} } @$data];
- }
-
- my $funds = $e->search_acq_fund($query);
-
- for my $fund (@$funds) {
- $fund->summary(retrieve_fund_summary_impl($e, $fund))
- if $$options{flesh_summary};
- $conn->respond($fund);
- }
-
- return undef;
-}
-
-__PACKAGE__->register_method(
- method => 'retrieve_fund_summary',
- api_name => 'open-ils.acq.fund.summary.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Returns a summary of credits/debits/encumbrances for a fund',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund id', type => 'number' }
- ],
- return => {desc => 'A hash of summary information, Event on failure'}
- }
-);
-
-sub retrieve_fund_summary {
- my($self, $conn, $auth, $fund_id) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- my $fund = $e->retrieve_acq_fund($fund_id) or return $e->event;
- return $e->event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
- return retrieve_fund_summary_impl($e, $fund);
-}
-
-
-sub retrieve_fund_summary_impl {
- my($e, $fund) = @_;
-
- my $at = $e->search_acq_fund_allocation_total({fund => $fund->id})->[0];
- my $dt = $e->search_acq_fund_debit_total({fund => $fund->id})->[0];
- my $et = $e->search_acq_fund_encumbrance_total({fund => $fund->id})->[0];
- my $st = $e->search_acq_fund_spent_total({fund => $fund->id})->[0];
- my $cb = $e->search_acq_fund_combined_balance({fund => $fund->id})->[0];
- my $sb = $e->search_acq_fund_spent_balance({fund => $fund->id})->[0];
-
- return {
- allocation_total => ($at) ? $at->amount : 0,
- debit_total => ($dt) ? $dt->amount : 0,
- encumbrance_total => ($et) ? $et->amount : 0,
- spent_total => ($st) ? $st->amount : 0,
- combined_balance => ($cb) ? $cb->amount : 0,
- spent_balance => ($sb) ? $sb->amount : 0,
- };
-}
-
-__PACKAGE__->register_method(
- method => 'transfer_money_between_funds',
- api_name => 'open-ils.acq.funds.transfer_money',
- signature => {
- desc => 'Method for transfering money between funds',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'Originating fund ID', type => 'number'},
- {desc => 'Amount of money to transfer away from the originating fund, in the same currency as said fund', type => 'number'},
- {desc => 'Destination fund ID', type => 'number'},
- {desc => 'Amount of money to transfer to the destination fund, in the same currency as said fund. If null, uses the same amount specified with the Originating Fund, and attempts a currency conversion if appropriate.', type => 'number'},
- {desc => 'Transfer Note', type => 'string'}
- ],
- return => {desc => '1 on success, Event on failure'}
- }
-);
-
-sub transfer_money_between_funds {
- my($self, $conn, $auth, $ofund_id, $ofund_amount, $dfund_id, $dfund_amount, $note) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- my $ofund = $e->retrieve_acq_fund($ofund_id) or return $e->event;
- return $e->die_event unless $e->allowed(['ADMIN_FUND','MANAGE_FUND'], $ofund->org, $ofund);
- my $dfund = $e->retrieve_acq_fund($dfund_id) or return $e->event;
- return $e->die_event unless $e->allowed(['ADMIN_FUND','MANAGE_FUND'], $dfund->org, $dfund);
-
- if (!defined $dfund_amount) {
- my $ratio = 1;
- if ($ofund->currency_type ne $dfund->currency_type) {
- my $exchange_rate = $e->json_query({
- "select"=>{"acqexr"=>["ratio"]},
- "from"=>"acqexr",
- "where"=>{
- "from_currency"=>$ofund->currency_type,
- "to_currency"=>$dfund->currency_type
- }
- });
- if (scalar(@$exchange_rate)<1) {
- $logger->error('Unable to find exchange rate for ' . $ofund->currency_type . ' to ' . $dfund->currency_type);
- return $e->die_event;
- }
- $ratio = @{$exchange_rate}[0]->{ratio};
- }
- $dfund_amount = $ofund_amount * $ratio;
- } else {
- return $e->die_event unless $e->allowed("ACQ_XFER_MANUAL_DFUND_AMOUNT");
- }
-
- $e->json_query({
- from => [
- 'acq.transfer_fund',
- $ofund_id, $ofund_amount, $dfund_id, $dfund_amount, $e->requestor->id, $note
- ]
- });
-
- $e->commit;
-
- return 1;
-}
-
-
-
-# ---------------------------------------------------------------
-# fund Allocations
-# ---------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'create_fund_alloc',
- api_name => 'open-ils.acq.fund_allocation.create',
- signature => {
- desc => 'Creates a new fund_allocation',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund allocation object to create', type => 'object'}
- ],
- return => {desc => 'The ID of the new fund_allocation'}
- }
-);
-
-sub create_fund_alloc {
- my($self, $conn, $auth, $fund_alloc) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- # this action is equivalent to both debiting a funding source and crediting a fund
-
- my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
- or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner);
-
- my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
-
- $fund_alloc->allocator($e->requestor->id);
- $e->create_acq_fund_allocation($fund_alloc) or return $e->die_event;
- $e->commit;
- return $fund_alloc->id;
-}
-
-
-__PACKAGE__->register_method(
- method => 'delete_fund_alloc',
- api_name => 'open-ils.acq.fund_allocation.delete',
- signature => {
- desc => 'Deletes a fund_allocation',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund Alocation ID', type => 'number'}
- ],
- return => {desc => '1 on success, Event on failure'}
- }
-);
-
-sub delete_fund_alloc {
- my($self, $conn, $auth, $fund_alloc_id) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- my $fund_alloc = $e->retrieve_acq_fund_allocation($fund_alloc_id) or return $e->die_event;
-
- my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
- or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner, $source);
-
- my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
-
- $e->delete_acq_fund_allocation($fund_alloc) or return $e->die_event;
- $e->commit;
- return 1;
-}
-
-__PACKAGE__->register_method(
- method => 'retrieve_fund_alloc',
- api_name => 'open-ils.acq.fund_allocation.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Retrieves a new fund_allocation',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund Allocation ID', type => 'number'}
- ],
- return => {desc => 'The fund allocation object on success, Event on failure'}
- }
-);
-
-sub retrieve_fund_alloc {
- my($self, $conn, $auth, $fund_alloc_id) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- my $fund_alloc = $e->retrieve_acq_fund_allocation($fund_alloc_id) or return $e->event;
-
- my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
- or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner, $source);
-
- my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
-
- return $fund_alloc;
-}
-
-
-__PACKAGE__->register_method(
- method => 'retrieve_funding_source_allocations',
- api_name => 'open-ils.acq.funding_source.allocations.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Retrieves a new fund_allocation',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'fund Allocation ID', type => 'number'}
- ],
- return => {desc => 'The fund allocation object on success, Event on failure'}
- }
-);
-
-sub retrieve_funding_source_allocations {
- my($self, $conn, $auth, $fund_alloc_id) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- my $fund_alloc = $e->retrieve_acq_fund_allocation($fund_alloc_id) or return $e->event;
-
- my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
- or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner, $source);
-
- my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
- return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
-
- return $fund_alloc;
-}
-
-# ----------------------------------------------------------------------------
-# Currency
-# ----------------------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'retrieve_all_currency_type',
- api_name => 'open-ils.acq.currency_type.all.retrieve',
- stream => 1,
- signature => {
- desc => 'Retrieves all currency_type objects',
- params => [
- {desc => 'Authentication token', type => 'string'},
- ],
- return => {desc => 'List of currency_type objects', type => 'list'}
- }
-);
-
-sub retrieve_all_currency_type {
- my($self, $conn, $auth, $fund_alloc_id) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- return $e->event unless $e->allowed('GENERAL_ACQ');
- $conn->respond($_) for @{$e->retrieve_all_acq_currency_type()};
-}
-
-__PACKAGE__->register_method(
- method => 'create_lineitem_assets',
- api_name => 'open-ils.acq.lineitem.assets.create',
- signature => {
- desc => q/Creates the bibliographic data, volume, and copies associated with a lineitem./,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'The lineitem id', type => 'number'},
- {desc => q/Options hash./}
- ],
- return => {desc => 'ID of newly created bib record, Event on error'}
- }
-);
-
-sub create_lineitem_assets {
- my($self, $conn, $auth, $li_id, $options) = @_;
- my $e = new_editor(authtoken=>$auth, xact=>1);
- return $e->die_event unless $e->checkauth;
- my ($count, $resp) = create_lineitem_assets_impl($e, $li_id, $options);
- return $resp if $resp;
- $e->commit;
- return $count;
-}
-
-sub create_lineitem_assets_impl {
- my($e, $li_id, $options) = @_;
- $options ||= {};
- my $evt;
-
- my $li = $e->retrieve_acq_lineitem([
- $li_id,
- { flesh => 1,
- flesh_fields => {jub => ['purchase_order', 'attributes']}
- }
- ]) or return (undef, $e->die_event);
-
- # -----------------------------------------------------------------
- # first, create the bib record if necessary
- # -----------------------------------------------------------------
- unless($li->eg_bib_id) {
-
- my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
- $e, $li->marc); #$rec->bib_source
-
- if($U->event_code($record)) {
- $e->rollback;
- return (undef, $record);
- }
-
- $li->editor($e->requestor->id);
- $li->edit_time('now');
- $li->eg_bib_id($record->id);
- $e->update_acq_lineitem($li) or return (undef, $e->die_event);
- }
-
- my $li_details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
-
- # -----------------------------------------------------------------
- # for each lineitem_detail, create the volume if necessary, create
- # a copy, and link them all together.
- # -----------------------------------------------------------------
- my %volcache;
- for my $li_detail_id (@{$li_details}) {
-
- my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
- or return (undef, $e->die_event);
-
- # Create the volume object if necessary
- my $volume = $volcache{$li_detail->cn_label};
- unless($volume and $volume->owning_lib == $li_detail->owning_lib) {
- ($volume, $evt) =
- OpenILS::Application::Cat::AssetCommon->find_or_create_volume(
- $e, $li_detail->cn_label, $li->eg_bib_id, $li_detail->owning_lib);
- return (undef, $evt) if $evt;
- $volcache{$volume->id} = $volume;
- }
-
- my $copy = Fieldmapper::asset::copy->new;
- $copy->isnew(1);
- $copy->loan_duration(2);
- $copy->fine_level(2);
- $copy->status(OILS_COPY_STATUS_ON_ORDER);
- $copy->barcode($li_detail->barcode);
- $copy->location($li_detail->location);
- $copy->call_number($volume->id);
- $copy->circ_lib($volume->owning_lib);
- $copy->circ_modifier($$options{circ_modifier} || 'book');
-
- $evt = OpenILS::Application::Cat::AssetCommon->create_copy($e, $volume, $copy);
- return (undef, $evt) if $evt;
-
- $li_detail->eg_copy_id($copy->id);
- $e->update_acq_lineitem_detail($li_detail) or return (undef, $e->die_event);
- }
-
- return (scalar @{$li_details});
-}
-
-
-
-
-sub create_purchase_order_impl {
- my($e, $p_order) = @_;
-
- $p_order->creator($e->requestor->id);
- $p_order->editor($e->requestor->id);
- $p_order->owner($e->requestor->id);
- $p_order->edit_time('now');
-
- return $e->die_event unless
- $e->allowed('CREATE_PURCHASE_ORDER', $p_order->ordering_agency);
-
- my $provider = $e->retrieve_acq_provider($p_order->provider)
- or return $e->die_event;
- return $e->die_event unless
- $e->allowed('MANAGE_PROVIDER', $provider->owner, $provider);
-
- $e->create_acq_purchase_order($p_order) or return $e->die_event;
- return undef;
-}
-
-
-__PACKAGE__->register_method(
- method => 'retrieve_all_user_purchase_order',
- api_name => 'open-ils.acq.purchase_order.user.all.retrieve',
- stream => 1,
- signature => {
- desc => 'Retrieves a purchase order',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'purchase_order to retrieve', type => 'number'},
- {desc => q/Options hash. flesh_lineitems: to get the lineitems and lineitem_attrs;
- clear_marc: to clear the MARC data from the lineitem (for reduced bandwidth);
- limit: number of items to return ,defaults to 50;
- offset: offset in the list of items to return
- order_by: sort the result, provide one or more colunm names, separated by commas,
- optionally followed by ASC or DESC as a single string
- li_limit : number of lineitems to return if fleshing line items;
- li_offset : lineitem offset if fleshing line items
- li_order_by : lineitem sort definition if fleshing line items
- flesh_lineitem_detail_count : flesh lineitem_detail_count field
- /,
- type => 'hash'}
- ],
- return => {desc => 'The purchase order, Event on failure'}
- }
-);
-
-sub retrieve_all_user_purchase_order {
- my($self, $conn, $auth, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- $options ||= {};
-
- # grab purchase orders I have
- my $perm_orgs = $U->user_has_work_perm_at($e, 'MANAGE_PROVIDER', {descendants =>1});
- return OpenILS::Event->new('PERM_FAILURE', ilsperm => 'MANAGE_PROVIDER')
- unless @$perm_orgs;
- my $provider_ids = $e->search_acq_provider({owner => $perm_orgs}, {idlist=>1});
- my $po_ids = $e->search_acq_purchase_order({provider => $provider_ids}, {idlist=>1});
-
- # grab my purchase orders
- push(@$po_ids, @{$e->search_acq_purchase_order({owner => $e->requestor->id}, {idlist=>1})});
-
- return undef unless @$po_ids;
-
- # now get the db to limit/sort for us
- $po_ids = $e->search_acq_purchase_order(
- [ {id => $po_ids}, {
- limit => $$options{limit} || 50,
- offset => $$options{offset} || 0,
- order_by => {acqpo => $$options{order_by} || 'create_time'}
- }
- ],
- {idlist => 1}
- );
-
- $conn->respond(retrieve_purchase_order_impl($e, $_, $options)) for @$po_ids;
- return undef;
-}
-
-
-__PACKAGE__->register_method(
- method => 'search_purchase_order',
- api_name => 'open-ils.acq.purchase_order.search',
- stream => 1,
- signature => {
- desc => 'Search for a purchase order',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => q/Search hash. Search fields include id, provider/, type => 'hash'}
- ],
- return => {desc => 'A stream of POs'}
- }
-);
-
-sub search_purchase_order {
- my($self, $conn, $auth, $search, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- my $po_ids = $e->search_acq_purchase_order($search, {idlist=>1});
- for my $po_id (@$po_ids) {
- $conn->respond($e->retrieve_acq_purchase_order($po_id))
- unless po_perm_failure($e, $po_id);
- }
-
- return undef;
-}
-
-
-
-__PACKAGE__->register_method(
- method => 'retrieve_purchase_order',
- api_name => 'open-ils.acq.purchase_order.retrieve',
- stream => 1,
- signature => {
- desc => 'Retrieves a purchase order',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'purchase_order to retrieve', type => 'number'},
- {desc => q/Options hash. flesh_lineitems, to get the lineitems and lineitem_attrs;
- clear_marc, to clear the MARC data from the lineitem (for reduced bandwidth)
- li_limit : number of lineitems to return if fleshing line items;
- li_offset : lineitem offset if fleshing line items
- li_order_by : lineitem sort definition if fleshing line items,
- flesh_po_items : po_item objects
- /,
- type => 'hash'}
- ],
- return => {desc => 'The purchase order, Event on failure'}
- }
-);
-
-sub retrieve_purchase_order {
- my($self, $conn, $auth, $po_id, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
-
- $po_id = [ $po_id ] unless ref $po_id;
- for ( @{$po_id} ) {
- my $rv;
- if ( po_perm_failure($e, $_) )
- { $rv = $e->event }
- else
- { $rv = retrieve_purchase_order_impl($e, $_, $options) }
-
- $conn->respond($rv);
- }
-
- return undef;
-}
-
-
-# if the user does not have permission to perform actions on this PO, return the perm failure event
-sub po_perm_failure {
- my($e, $po_id, $fund_id) = @_;
- my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->event;
- return $e->event unless $e->allowed('VIEW_PURCHASE_ORDER', $po->ordering_agency, $po);
- return undef;
-}
-
-sub build_price_summary {
- my ($e, $po_id) = @_;
-
- # TODO: Add summary value for estimated amount (pre-encumber)
-
- # fetch the fund debits for this purchase order
- my $debits = $e->json_query({
- "select" => {"acqfdeb" => [qw/encumbrance amount/]},
- "from" => {
- "acqlid" => {
- "jub" => {
- "fkey" => "lineitem",
- "field" => "id",
- "join" => {
- "acqpo" => {
- "fkey" => "purchase_order", "field" => "id"
- }
- }
- },
- "acqfdeb" => {"fkey" => "fund_debit", "field" => "id"}
- }
- },
- "where" => {"+acqpo" => {"id" => $po_id}}
- });
-
- # add any debits for non-bib po_items
- push(@$debits, @{
- $e->json_query({
- "select" => {"acqfdeb" => [qw/encumbrance amount/]},
- "from" => {acqpoi => 'acqfdeb'},
- "where" => {"+acqpoi" => {"purchase_order" => $po_id}}
- })
- });
-
- my ($enc, $spent) = (0, 0);
- for my $deb (@$debits) {
- if($U->is_true($deb->{encumbrance})) {
- $enc += $deb->{amount};
- } else {
- $spent += $deb->{amount};
- }
- }
- ($enc, $spent);
-}
-
-
-sub retrieve_purchase_order_impl {
- my($e, $po_id, $options) = @_;
-
- my $flesh = {"flesh" => 1, "flesh_fields" => {"acqpo" => []}};
-
- $options ||= {};
- unless ($options->{"no_flesh_cancel_reason"}) {
- push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "cancel_reason";
- }
- if ($options->{"flesh_notes"}) {
- push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "notes";
- }
- if ($options->{"flesh_provider"}) {
- push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "provider";
- }
-
- push (@{$flesh->{flesh_fields}->{acqpo}}, 'po_items') if $options->{flesh_po_items};
-
- my $args = (@{$flesh->{"flesh_fields"}->{"acqpo"}}) ?
- [$po_id, $flesh] : $po_id;
-
- my $po = $e->retrieve_acq_purchase_order($args)
- or return $e->event;
-
- if($$options{flesh_lineitems}) {
-
- my $flesh_fields = { jub => ['attributes'] };
- $flesh_fields->{jub}->[1] = 'lineitem_details' if $$options{flesh_lineitem_details};
- $flesh_fields->{acqlid} = ['fund_debit'] if $$options{flesh_fund_debit};
-
- my $items = $e->search_acq_lineitem([
- {purchase_order => $po_id},
- {
- flesh => 3,
- flesh_fields => $flesh_fields,
- limit => $$options{li_limit} || 50,
- offset => $$options{li_offset} || 0,
- order_by => {jub => $$options{li_order_by} || 'create_time'}
- }
- ]);
-
- if($$options{clear_marc}) {
- $_->clear_marc for @$items;
- }
-
- $po->lineitems($items);
- $po->lineitem_count(scalar(@$items));
-
- } elsif( $$options{flesh_lineitem_ids} ) {
- $po->lineitems($e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1}));
-
- } elsif( $$options{flesh_lineitem_count} ) {
-
- my $items = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist=>1});
- $po->lineitem_count(scalar(@$items));
- }
-
- if($$options{flesh_price_summary}) {
- my ($enc, $spent) = build_price_summary($e, $po_id);
- $po->amount_encumbered($enc);
- $po->amount_spent($spent);
- }
-
- return $po;
-}
-
-
-__PACKAGE__->register_method(
- method => 'format_po',
- api_name => 'open-ils.acq.purchase_order.format'
-);
-
-sub format_po {
- my($self, $conn, $auth, $po_id, $format) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
-
- my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->event;
- return $e->event unless $e->allowed('VIEW_PURCHASE_ORDER', $po->ordering_agency);
-
- my $hook = "format.po.$format";
- return $U->fire_object_event(undef, $hook, $po, $po->ordering_agency);
-}
-
-__PACKAGE__->register_method(
- method => 'format_lineitem',
- api_name => 'open-ils.acq.lineitem.format'
-);
-
-sub format_lineitem {
- my($self, $conn, $auth, $li_id, $format, $user_data) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
-
- my $li = $e->retrieve_acq_lineitem($li_id) or return $e->event;
-
- my $context_org;
- if (defined $li->purchase_order) {
- my $po = $e->retrieve_acq_purchase_order($li->purchase_order) or return $e->die_event;
- return $e->event unless $e->allowed('VIEW_PURCHASE_ORDER', $po->ordering_agency);
- $context_org = $po->ordering_agency;
- } else {
- my $pl = $e->retrieve_acq_picklist($li->picklist) or return $e->die_event;
- if($e->requestor->id != $pl->owner) {
- return $e->event unless
- $e->allowed('VIEW_PICKLIST', $pl->org_unit, $pl);
- }
- $context_org = $pl->org_unit;
- }
-
- my $hook = "format.acqli.$format";
- return $U->fire_object_event(undef, $hook, $li, $context_org, 'print-on-demand', $user_data);
-}
-
-__PACKAGE__->register_method (
- method => 'po_events',
- api_name => 'open-ils.acq.purchase_order.events.owner',
- stream => 1,
- signature => q/
- Retrieve EDI-related purchase order events (format.po.jedi), by default those which are pending.
- @param authtoken Login session key
- @param owner Id or array of id's for the purchase order Owner field. Filters the events to just those pertaining to PO's meeting this criteria.
- @param options Object for tweaking the selection criteria and fleshing options.
- /
-);
-
-__PACKAGE__->register_method (
- method => 'po_events',
- api_name => 'open-ils.acq.purchase_order.events.ordering_agency',
- stream => 1,
- signature => q/
- Retrieve EDI-related purchase order events (format.po.jedi), by default those which are pending.
- @param authtoken Login session key
- @param owner Id or array of id's for the purchase order Ordering Agency field. Filters the events to just those pertaining to PO's meeting this criteria.
- @param options Object for tweaking the selection criteria and fleshing options.
- /
-);
-
-__PACKAGE__->register_method (
- method => 'po_events',
- api_name => 'open-ils.acq.purchase_order.events.id',
- stream => 1,
- signature => q/
- Retrieve EDI-related purchase order events (format.po.jedi), by default those which are pending.
- @param authtoken Login session key
- @param owner Id or array of id's for the purchase order Id field. Filters the events to just those pertaining to PO's meeting this criteria.
- @param options Object for tweaking the selection criteria and fleshing options.
- /
-);
-
-sub po_events {
- my($self, $conn, $auth, $search_value, $options) = @_;
- my $e = new_editor(authtoken => $auth);
- return $e->event unless $e->checkauth;
-
- (my $search_field = $self->api_name) =~ s/.*\.([_a-z]+)$/$1/;
- my $obj_type = 'acqpo';
-
- if ($search_field eq 'ordering_agency') {
- $search_value = $U->get_org_descendants($search_value);
- }
-
- my $query = {
- "select"=>{"atev"=>["id"]},
- "from"=>"atev",
- "where"=>{
- "target"=>{
- "in"=>{
- "select"=>{$obj_type=>["id"]},
- "from"=>$obj_type,
- "where"=>{$search_field=>$search_value}
- }
- },
- "event_def"=>{
- "in"=>{
- "select"=>{atevdef=>["id"]},
- "from"=>"atevdef",
- "where"=>{
- "hook"=>"format.po.jedi"
- }
- }
- },
- "state"=>"pending"
- },
- "order_by"=>[{"class"=>"atev", "field"=>"run_time", "direction"=>"desc"}]
- };
-
- if ($options && defined $options->{state}) {
- $query->{'where'}{'state'} = $options->{state}
- }
-
- if ($options && defined $options->{start_time}) {
- $query->{'where'}{'start_time'} = $options->{start_time};
- }
-
- if ($options && defined $options->{order_by}) {
- $query->{'order_by'} = $options->{order_by};
- }
- my $po_events = $e->json_query($query);
-
- my $flesh_fields = { 'atev' => [ 'event_def' ] };
- my $flesh_depth = 1;
-
- for my $id (@$po_events) {
- my $event = $e->retrieve_action_trigger_event([
- $id->{id},
- {flesh => $flesh_depth, flesh_fields => $flesh_fields}
- ]);
- if (! $event) { next; }
-
- my $po = retrieve_purchase_order_impl(
- $e,
- $event->target(),
- {flesh_lineitem_count=>1,flesh_price_summary=>1}
- );
-
- if ($e->allowed( ['CREATE_PURCHASE_ORDER','VIEW_PURCHASE_ORDER'], $po->ordering_agency() )) {
- $event->target( $po );
- $conn->respond($event);
- }
- }
-
- return undef;
-}
-
-__PACKAGE__->register_method (
- method => 'update_po_events',
- api_name => 'open-ils.acq.purchase_order.event.cancel.batch',
- stream => 1,
-);
-__PACKAGE__->register_method (
- method => 'update_po_events',
- api_name => 'open-ils.acq.purchase_order.event.reset.batch',
- stream => 1,
-);
-
-sub update_po_events {
- my($self, $conn, $auth, $event_ids) = @_;
- my $e = new_editor(xact => 1, authtoken => $auth);
- return $e->die_event unless $e->checkauth;
-
- my $x = 1;
- for my $id (@$event_ids) {
-
- # do a little dance to determine what libraries we are ultimately affecting
- my $event = $e->retrieve_action_trigger_event([
- $id,
- { flesh => 2,
- flesh_fields => {atev => ['event_def'], atevdef => ['hook']}
- }
- ]) or return $e->die_event;
-
- my $po = retrieve_purchase_order_impl(
- $e,
- $event->target(),
- {}
- );
-
- return $e->die_event unless $e->allowed( ['CREATE_PURCHASE_ORDER','VIEW_PURCHASE_ORDER'], $po->ordering_agency() );
-
- if($self->api_name =~ /cancel/) {
- $event->state('invalid');
- } elsif($self->api_name =~ /reset/) {
- $event->clear_start_time;
- $event->clear_update_time;
- $event->state('pending');
- }
-
- $e->update_action_trigger_event($event) or return $e->die_event;
- $conn->respond({maximum => scalar(@$event_ids), progress => $x++});
- }
-
- $e->commit;
- return {complete => 1};
-}
-
-
-__PACKAGE__->register_method (
- method => 'process_fiscal_rollover',
- api_name => 'open-ils.acq.fiscal_rollover.combined',
- stream => 1,
- signature => {
- desc => q/
- Performs a combined fiscal fund rollover process.
-
- Creates a new series of funds for the following year, copying the old years
- funds that are marked as propagable. They apply to the funds belonging to
- either an org unit or to an org unit and all of its dependent org units.
- The procedures may be run repeatedly; if any fund has already been propagated,
- both the old and the new funds will be left alone.
-
- Closes out any applicable funds (by org unit or by org unit and dependents)
- that are marked as propagable. If such a fund has not already been propagated
- to the new year, it will be propagated at closing time.
-
- If a fund is marked as subject to rollover, any unspent balance in the old year's
- fund (including money encumbered but not spent) is transferred to the new year's
- fund. Otherwise it is deallocated back to the funding source(s).
-
- In either case, any encumbrance debits are transferred to the new fund, along
- with the corresponding lineitem details. The old year's fund is marked as inactive
- so that new debits may not be charged to it.
- /,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'Fund Year to roll over', type => 'integer'},
- {desc => 'Org unit ID', type => 'integer'},
- {desc => 'Include Descendant Orgs (boolean)', type => 'integer'},
- ],
- return => {desc => 'Returns a stream of all related funds for the next year including fund summary for each'}
- }
-
-);
-
-__PACKAGE__->register_method (
- method => 'process_fiscal_rollover',
- api_name => 'open-ils.acq.fiscal_rollover.combined.dry_run',
- stream => 1,
- signature => {
- desc => q/
- @see open-ils.acq.fiscal_rollover.combined
- This is the dry-run version. The action is performed,
- new fund information is returned, then all changes are rolled back.
- /
- }
-
-);
-
-__PACKAGE__->register_method (
- method => 'process_fiscal_rollover',
- api_name => 'open-ils.acq.fiscal_rollover.propagate',
- stream => 1,
- signature => {
- desc => q/
- @see open-ils.acq.fiscal_rollover.combined
- This version performs fund propagation only. I.e, creation of
- the following year's funds. It does not rollover over balances, encumbrances,
- or mark the previous year's funds as complete.
- /
- }
-);
-
-__PACKAGE__->register_method (
- method => 'process_fiscal_rollover',
- api_name => 'open-ils.acq.fiscal_rollover.propagate.dry_run',
- stream => 1,
- signature => { desc => q/
- @see open-ils.acq.fiscal_rollover.propagate
- This is the dry-run version. The action is performed,
- new fund information is returned, then all changes are rolled back.
- / }
-);
-
-
-
-sub process_fiscal_rollover {
- my( $self, $conn, $auth, $year, $org_id, $descendants, $options ) = @_;
-
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- return $e->die_event unless $e->allowed('ADMIN_FUND', $org_id);
- $options ||= {};
-
- my $combined = ($self->api_name =~ /combined/);
-
- my $org_ids = ($descendants) ?
- [
- map
- { $_->{id} } # fetch my descendants
- @{$e->json_query({from => ['actor.org_unit_descendants', $org_id]})}
- ]
- : [$org_id];
-
- # Create next year's funds
- # Note, it's safe to run this more than once.
- # IOW, it will not create duplicate new funds.
- $e->json_query({
- from => [
- ($descendants) ?
- 'acq.propagate_funds_by_org_tree' :
- 'acq.propagate_funds_by_org_unit',
- $year, $e->requestor->id, $org_id
- ]
- });
-
- if($combined) {
-
- # Roll the uncumbrances over to next year's funds
- # Mark the funds for $year as inactive
-
- $e->json_query({
- from => [
- ($descendants) ?
- 'acq.rollover_funds_by_org_tree' :
- 'acq.rollover_funds_by_org_unit',
- $year, $e->requestor->id, $org_id
- ]
- });
- }
-
- # Fetch all funds for the specified org units for the subsequent year
- my $fund_ids = $e->search_acq_fund([
- {
- year => int($year) + 1,
- org => $org_ids,
- propagate => 't'
- }, {
- limit => $$options{limit} || 20,
- offset => $$options{offset} || 0,
- }
- ],
- {idlist => 1}
- );
-
- foreach (@$fund_ids) {
- my $fund = $e->retrieve_acq_fund($_) or return $e->die_event;
- $fund->summary(retrieve_fund_summary_impl($e, $fund));
-
- my $amount = 0;
- if($combined and $U->is_true($fund->rollover)) {
- # see how much money was rolled over
-
- my $sum = $e->json_query({
- select => {acqftr => [{column => 'dest_amount', transform => 'sum'}]},
- from => 'acqftr',
- where => {dest_fund => $fund->id, note => 'Rollover'}
- })->[0];
-
- $amount = $sum->{dest_amount} if $sum;
- }
-
- $conn->respond({fund => $fund, rollover_amount => $amount});
- }
-
- $self->api_name =~ /dry_run/ and $e->rollback or $e->commit;
- return undef;
-}
-
-
-1;
-
+++ /dev/null
-package OpenILS::Application::Acq::Invoice;
-use base qw/OpenILS::Application/;
-use strict; use warnings;
-
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Event;
-my $U = 'OpenILS::Application::AppUtils';
-
-
-__PACKAGE__->register_method(
- method => 'build_invoice_api',
- api_name => 'open-ils.acq.invoice.update',
- signature => {
- desc => q/Creates, updates, and deletes invoices, and related invoice entries, and invoice items/,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => q/Invoice/, type => 'number'},
- {desc => q/Entries. Array of 'acqie' objects/, type => 'array'},
- {desc => q/Items. Array of 'acqii' objects/, type => 'array'},
- ],
- return => {desc => 'The invoice w/ entries and items attached', type => 'object', class => 'acqinv'}
- }
-);
-
-sub build_invoice_api {
- my($self, $conn, $auth, $invoice, $entries, $items) = @_;
-
- my $e = new_editor(xact => 1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- my $evt;
-
- if(ref $invoice) {
- if($invoice->isnew) {
- $invoice->receiver($e->requestor->ws_ou) unless $invoice->receiver;
- $invoice->recv_method('PPR') unless $invoice->recv_method;
- $invoice->recv_date('now') unless $invoice->recv_date;
- $e->create_acq_invoice($invoice) or return $e->die_event;
- } elsif($invoice->isdeleted) {
- i$e->delete_acq_invoice($invoice) or return $e->die_event;
- } else {
- $e->update_acq_invoice($invoice) or return $e->die_event;
- }
- } else {
- # caller only provided the ID
- $invoice = $e->retrieve_acq_invoice($invoice) or return $e->die_event;
- }
-
- return $e->die_event unless $e->allowed('CREATE_INVOICE', $invoice->receiver);
-
- if($entries) {
- for my $entry (@$entries) {
- $entry->invoice($invoice->id);
-
- if($entry->isnew) {
-
- $e->create_acq_invoice_entry($entry) or return $e->die_event;
- return $evt if $evt = update_entry_debits($e, $entry);
-
- } elsif($entry->isdeleted) {
-
- return $evt if $evt = rollback_entry_debits($e, $entry);
- $e->delete_acq_invoice_entry($entry) or return $e->die_event;
-
- } elsif($entry->ischanged) {
-
- my $orig_entry = $e->retrieve_acq_invoice_entry($entry->id) or return $e->die_event;
-
- if($orig_entry->amount_paid != $entry->amount_paid or
- $entry->phys_item_count != $orig_entry->phys_item_count) {
-
- return $evt if $evt = rollback_entry_debits($e, $orig_entry);
- return $evt if $evt = update_entry_debits($e, $entry);
-
- }
-
- $e->update_acq_invoice_entry($entry) or return $e->die_event;
- }
- }
- }
-
- if($items) {
- for my $item (@$items) {
- $item->invoice($invoice->id);
-
- if($item->isnew) {
-
- $e->create_acq_invoice_item($item) or return $e->die_event;
-
- # future: cache item types
- my $item_type = $e->retrieve_acq_invoice_item_type(
- $item->inv_item_type) or return $e->die_event;
-
- # prorated items are handled separately
- unless($U->is_true($item_type->prorate)) {
- my $debit;
- if($item->po_item) {
- my $po_item = $e->retrieve_acq_po_item($item->po_item) or return $e->die_event;
- $debit = $e->retrieve_acq_fund_debit($po_item->fund_debit) or return $e->die_event;
- } else {
- $debit = Fieldmapper::acq::fund_debit->new;
- $debit->isnew(1);
- }
- $debit->fund($item->fund);
- $debit->amount($item->amount_paid);
- $debit->origin_amount($item->amount_paid);
- $debit->origin_currency_type($e->retrieve_acq_fund($item->fund)->currency_type); # future: cache funds locally
- $debit->encumbrance('f');
- $debit->debit_type('direct_charge');
-
- if($debit->isnew) {
- $e->create_acq_fund_debit($debit) or return $e->die_event;
- } else {
- $e->update_acq_fund_debit($debit) or return $e->die_event;
- }
-
- $item->fund_debit($debit->id);
- $e->update_acq_invoice_item($item) or return $e->die_event;
- }
-
- } elsif($item->isdeleted) {
-
- $e->delete_acq_invoice_item($item) or return $e->die_event;
-
- if($item->po_item and $e->retrieve_acq_po_item($item->po_item)->fund_debit == $item->fund_debit) {
- # the debit is attached to the po_item. instead of deleting it, roll it back
- # to being an encumbrance. Note: a prorated invoice_item that points to a po_item
- # could point to a different fund_debit. We can't go back in time to collect all the
- # prorated invoice_items (nor is the caller asking us too), so when that happens,
- # just delete the extraneous debit (in the else block).
- my $debit = $e->retrieve_acq_fund_debit($item->fund_debit);
- $debit->encumbrance('t');
- $e->update_acq_fund_debit($debit) or return $e->die_event;
- } else {
- $e->delete_acq_fund_debit($e->retrieve_acq_fund_debit($item->fund_debit))
- or return $e->die_event;
- }
-
-
- } elsif($item->ischanged) {
-
- my $debit = $e->retrieve_acq_fund_debit($item->fund_debit) or return $e->die_event;
- $debit->amount($item->amount_paid);
- $debit->fund($item->fund);
- $e->update_acq_fund_debit($debit) or return $e->die_event;
- $e->update_acq_invoice_item($item) or return $e->die_event;
- }
- }
- }
-
- $invoice = fetch_invoice_impl($e, $invoice->id);
- $e->commit;
-
- return $invoice;
-}
-
-
-sub rollback_entry_debits {
- my($e, $entry) = @_;
- my $debits = find_entry_debits($e, $entry, 'f', entry_amount_per_item($entry));
- my $lineitem = $e->retrieve_acq_lineitem($entry->lineitem) or return $e->die_event;
-
- for my $debit (@$debits) {
- # revert to the original estimated amount re-encumber
- $debit->encumbrance('t');
- $debit->amount($lineitem->estimated_unit_price());
- $e->update_acq_fund_debit($debit) or return $e->die_event;
- update_copy_cost($e, $debit) or return $e->die_event; # clear the cost
- }
-
- return undef;
-}
-
-sub update_entry_debits {
- my($e, $entry) = @_;
-
- my $debits = find_entry_debits($e, $entry, 't');
- return undef unless @$debits;
-
- if($entry->phys_item_count > @$debits) {
- $e->rollback;
- # We can't invoice for more items than we have debits for
- return OpenILS::Event->new(
- 'ACQ_INVOICE_ENTRY_COUNT_EXCEEDS_DEBITS',
- payload => {entry => $entry->id});
- }
-
- for my $debit (@$debits) {
- my $amount = entry_amount_per_item($entry);
- $debit->amount($amount);
- $debit->encumbrance('f');
- $e->update_acq_fund_debit($debit) or return $e->die_event;
-
- # TODO: this does not reflect ancillary charges, like taxes, etc.
- # We may need a way to indicate whether the amount attached to an
- # invoice_item should be prorated and included in the copy cost.
- # Note that acq.invoice_item_type.prorate does not necessarily
- # mean a charge should be included in the copy price, only that
- # it should spread accross funds.
- update_copy_cost($e, $debit, $amount) or return $e->die_event;
- }
-
- return undef;
-}
-
-# update the linked copy to reflect the amount paid for the item
-# returns true on success, false on error
-sub update_copy_cost {
- my ($e, $debit, $amount) = @_;
-
- my $lid = $e->search_acq_lineitem_detail([
- {fund_debit => $debit->id},
- {flesh => 1, flesh_fields => {acqlid => ['eg_copy_id']}}
- ])->[0];
-
- if($lid and my $copy = $lid->eg_copy_id) {
- defined $amount and $copy->cost($amount) or $copy->clear_cost;
- $copy->editor($e->requestor->id);
- $copy->edit_date('now');
- $e->update_asset_copy($copy) or return 0;
- }
-
- return 1;
-}
-
-
-sub entry_amount_per_item {
- my $entry = shift;
- return $entry->amount_paid if $U->is_true($entry->billed_per_item);
- return 0 if $entry->phys_item_count == 0;
- return $entry->amount_paid / $entry->phys_item_count;
-}
-
-sub easy_money { # TODO XXX replace with something from a library
- my ($val) = @_;
-
- my $rounded = int($val * 100) / 100.0;
- if ($rounded == $val) {
- return sprintf("%.02f", $val);
- } else {
- return sprintf("%g", $val);
- }
-}
-
-# 0 on failure (caller should call $e->die_event), array on success
-sub amounts_spent_per_fund {
- my ($e, $inv_id) = @_;
-
- my $entries = $e->search_acq_invoice_entry({"invoice" => $inv_id}) or
- return 0;
-
- my $items = $e->search_acq_invoice_item({"invoice" => $inv_id}) or
- return 0;
-
- my %totals_by_fund;
- foreach my $entry (@$entries) {
- my $debits = find_entry_debits($e, $entry, "f") or return 0;
- foreach (@$debits) {
- $totals_by_fund{$_->fund} ||= 0.0;
- $totals_by_fund{$_->fund} += $_->amount;
- }
- }
-
- foreach my $item (@$items) {
- next unless $item->fund and $item->amount_paid;
- $totals_by_fund{$item->fund} ||= 0.0;
- $totals_by_fund{$item->fund} += $item->amount_paid;
- }
-
- my @totals;
- foreach my $fund_id (keys %totals_by_fund) {
- my $fund = $e->retrieve_acq_fund($fund_id) or return 0;
- push @totals, {
- "fund" => $fund->to_bare_hash,
- "total" => easy_money($totals_by_fund{$fund_id})
- };
- }
-
- return \@totals;
-}
-
-# there is no direct link between invoice_entry and fund debits.
-# when we need to retrieve the related debits, we have to do some searching
-sub find_entry_debits {
- my($e, $entry, $encumbrance, $amount) = @_;
-
- my $query = {
- select => {acqfdeb => ['id']},
- from => {
- acqfdeb => {
- acqlid => {
- join => {
- jub => {
- join => {
- acqie => {
- filter => {id => $entry->id}
- }
- }
- }
- }
- }
- }
- },
- where => {'+acqfdeb' => {encumbrance => $encumbrance}},
- order_by => {'acqlid' => ['recv_time']}, # un-received items will sort to the end
- limit => $entry->phys_item_count
- };
-
- $query->{where}->{'+acqfdeb'}->{amount} = $amount if $amount;
-
- my $debits = $e->json_query($query);
- my $debit_ids = [map { $_->{id} } @$debits];
- return (@$debit_ids) ? $e->search_acq_fund_debit({id => $debit_ids}) : [];
-}
-
-
-__PACKAGE__->register_method(
- method => 'build_invoice_api',
- api_name => 'open-ils.acq.invoice.retrieve',
- authoritative => 1,
- signature => {
- desc => q/Creates a new stub invoice/,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => q/Invoice Id/, type => 'number'},
- ],
- return => {desc => 'The new invoice w/ entries and items attached', type => 'object', class => 'acqinv'}
- }
-);
-
-
-sub fetch_invoice_api {
- my($self, $conn, $auth, $invoice_id, $options) = @_;
-
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
-
- my $invoice = fetch_invoice_impl($e, $invoice_id, $options) or
- return $e->event;
- return $e->event unless $e->allowed(['VIEW_INVOICE', 'CREATE_INVOICE'], $invoice->receiver);
-
- return $invoice;
-}
-
-sub fetch_invoice_impl {
- my ($e, $invoice_id, $options) = @_;
-
- $options ||= {};
-
- my $args = $options->{"no_flesh_misc"} ? $invoice_id : [
- $invoice_id,
- {
- "flesh" => 6,
- "flesh_fields" => {
- "acqinv" => ["entries", "items"],
- "acqii" => ["fund_debit", "purchase_order", "po_item"]
- }
- }
- ];
-
- return $e->retrieve_acq_invoice($args);
-}
-
-__PACKAGE__->register_method(
- method => 'prorate_invoice',
- api_name => 'open-ils.acq.invoice.apply_prorate',
- signature => {
- desc => q/
- For all invoice items that have the prorate flag set to true, this will create the necessary
- additional invoice_item's to prorate the cost across all affected funds by percent spent for each fund.
- /,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => q/Invoice Id/, type => 'number'},
- ],
- return => {desc => 'The updated invoice w/ entries and items attached', type => 'object', class => 'acqinv'}
- }
-);
-
-
-sub prorate_invoice {
- my($self, $conn, $auth, $invoice_id) = @_;
-
- my $e = new_editor(xact => 1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- my $invoice = fetch_invoice_impl($e, $invoice_id) or return $e->die_event;
- return $e->die_event unless $e->allowed('CREATE_INVOICE', $invoice->receiver);
-
- my @lid_debits;
- push(@lid_debits, @{find_entry_debits($e, $_, 'f', entry_amount_per_item($_))}) for @{$invoice->entries};
-
- my $inv_items = $e->search_acq_invoice_item([
- {"invoice" => $invoice_id, "fund_debit" => {"!=" => undef}},
- {"flesh" => 1, "flesh_fields" => {"acqii" => ["fund_debit"]}}
- ]) or return $e->die_event;
-
- my @item_debits = map { $_->fund_debit } @$inv_items;
-
- my %fund_totals;
- my $total_entry_paid = 0;
- for my $debit (@lid_debits, @item_debits) {
- $fund_totals{$debit->fund} = 0 unless $fund_totals{$debit->fund};
- $fund_totals{$debit->fund} += $debit->amount;
- $total_entry_paid += $debit->amount;
- }
-
- $logger->info("invoice: prorating against invoice amount $total_entry_paid");
-
- for my $item (@{$invoice->items}) {
-
- next if $item->fund_debit; # item has already been processed
-
- # future: cache item types locally
- my $item_type = $e->retrieve_acq_invoice_item_type($item->inv_item_type) or return $e->die_event;
- next unless $U->is_true($item_type->prorate);
-
- # Prorate charges across applicable funds
- my $full_item_paid = $item->amount_paid; # total amount paid for this item before splitting
- my $full_item_cost = $item->cost_billed; # total amount invoiced for this item before splitting
- my $first_round = 1;
- my $largest_debit;
- my $largest_item;
- my $total_debited = 0;
- my $total_costed = 0;
-
- for my $fund_id (keys %fund_totals) {
-
- my $spent_for_fund = $fund_totals{$fund_id};
- next unless $spent_for_fund > 0;
-
- my $prorated_amount = ($spent_for_fund / $total_entry_paid) * $full_item_paid;
- my $prorated_cost = ($spent_for_fund / $total_entry_paid) * $full_item_cost;
- $logger->info("invoice: attaching prorated amount $prorated_amount to fund $fund_id for invoice $invoice_id");
-
- my $debit;
- if($first_round and $item->po_item) {
- # if this item is the result of a PO item, repurpose the original debit
- # for the first chunk of the prorated amount
- $debit = $e->retrieve_acq_fund_debit($item->po_item->fund_debit);
- } else {
- $debit = Fieldmapper::acq::fund_debit->new;
- $debit->isnew(1);
- }
-
- $debit->fund($fund_id);
- $debit->amount($prorated_amount);
- $debit->origin_amount($prorated_amount);
- $debit->origin_currency_type($e->retrieve_acq_fund($fund_id)->currency_type); # future: cache funds locally
- $debit->encumbrance('f');
- $debit->debit_type('prorated_charge');
-
- if($debit->isnew) {
- $e->create_acq_fund_debit($debit) or return $e->die_event;
- } else {
- $e->update_acq_fund_debit($debit) or return $e->die_event;
- }
-
- $total_debited += $prorated_amount;
- $total_costed += $prorated_cost;
- $largest_debit = $debit if !$largest_debit or $prorated_amount > $largest_debit->amount;
-
- if($first_round) {
-
- # re-purpose the original invoice_item for the first prorated amount
- $item->fund($fund_id);
- $item->fund_debit($debit->id);
- $item->amount_paid($prorated_amount);
- $item->cost_billed($prorated_cost);
- $e->update_acq_invoice_item($item) or return $e->die_event;
- $largest_item = $item if !$largest_item or $prorated_amount > $largest_item->amount_paid;
-
- } else {
-
- # for subsequent prorated amounts, create a new invoice_item
- my $new_item = $item->clone;
- $new_item->clear_id;
- $new_item->fund($fund_id);
- $new_item->fund_debit($debit->id);
- $new_item->amount_paid($prorated_amount);
- $new_item->cost_billed($prorated_cost);
- $e->create_acq_invoice_item($new_item) or return $e->die_event;
- $largest_item = $new_item if !$largest_item or $prorated_amount > $largest_item->amount_paid;
- }
-
- $first_round = 0;
- }
-
- # make sure the percentages didn't leave a small sliver of money over/under-debited
- # if so, tweak the largest debit to smooth out the difference
- if($total_debited != $full_item_paid or $total_costed != $full_item_cost) {
-
- my $paid_diff = $full_item_paid - $total_debited;
- my $cost_diff = $full_item_cost - $total_debited;
- $logger->info("invoice: repairing prorate descrepency of paid:$paid_diff and cost:$cost_diff");
- my $new_paid = $largest_item->amount_paid + $paid_diff;
- my $new_cost = $largest_item->cost_billed + $cost_diff;
-
- $largest_debit = $e->retrieve_acq_fund_debit($largest_debit->id); # get latest copy
- $largest_debit->amount($new_paid);
- $e->update_acq_fund_debit($largest_debit) or return $e->die_event;
-
- $largest_item = $e->retrieve_acq_invoice_item($largest_item->id); # get latest copy
- $largest_item->amount_paid($new_paid);
- $largest_item->cost_billed($new_cost);
-
- $e->update_acq_invoice_item($largest_item) or return $e->die_event;
- }
- }
-
- $invoice = fetch_invoice_impl($e, $invoice_id);
- $e->commit;
-
- return $invoice;
-}
-
-
-__PACKAGE__->register_method(
- method => "print_html_invoice",
- api_name => "open-ils.acq.invoice.print.html",
- stream => 1,
- signature => {
- desc => "Retrieve printable HTML vouchers for each given invoice",
- params => [
- {desc => "Authentication token", type => "string"},
- {desc => "Invoice ID or a list of them", type => "mixed"},
- ],
- return => {
- desc => q{One A/T event containing a printable HTML voucher for
- each given invoice},
- type => "object", class => "atev"}
- }
-);
-
-
-sub print_html_invoice {
- my ($self, $conn, $auth, $id_list) = @_;
-
- my $e = new_editor("authtoken" => $auth);
- return $e->die_event unless $e->checkauth;
-
- $id_list = [$id_list] unless ref $id_list;
-
- my $invoices = $e->search_acq_invoice({"id" => $id_list}) or
- return $e->die_event;
-
- foreach my $invoice (@$invoices) {
- return $e->die_event unless
- $e->allowed("VIEW_INVOICE", $invoice->receiver);
-
- my $amounts = amounts_spent_per_fund($e, $invoice->id) or
- return $e->die_event;
-
- $conn->respond(
- $U->fire_object_event(
- undef, "format.acqinv.html", $invoice, $invoice->receiver,
- "print-on-demand", $amounts
- )
- );
- }
-
- $e->disconnect;
- undef;
-}
-
-1;
+++ /dev/null
-package OpenILS::Application::Acq::Lineitem;
-use base qw/OpenILS::Application/;
-use strict; use warnings;
-
-use OpenILS::Event;
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenILS::Const qw/:const/;
-use OpenSRF::Utils::SettingsClient;
-use OpenILS::Application::AppUtils;
-use OpenILS::Application::Acq::Financials;
-use OpenILS::Application::Cat::BibCommon;
-use OpenILS::Application::Cat::AssetCommon;
-my $U = 'OpenILS::Application::AppUtils';
-
-
-__PACKAGE__->register_method(
- method => 'create_lineitem',
- api_name => 'open-ils.acq.lineitem.create',
- signature => {
- desc => 'Creates a lineitem',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'The lineitem object to create', type => 'object'},
- ],
- return => {desc => 'ID of newly created lineitem on success, Event on error'}
- }
-);
-
-sub create_lineitem {
- my($self, $conn, $auth, $li) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
-
- if($li->picklist) {
- my $picklist = $e->retrieve_acq_picklist($li->picklist)
- or return $e->die_event;
-
- if($picklist->owner != $e->requestor->id) {
- return $e->die_event unless
- $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
- }
-
- # indicate the picklist was updated
- $picklist->edit_time('now');
- $picklist->editor($e->requestor->id);
- $e->update_acq_picklist($picklist) or return $e->die_event;
- }
-
- if($li->purchase_order) {
- my $po = $e->retrieve_acq_purchase_order($li->purchase_order)
- or return $e->die_event;
- return $e->die_event unless
- $e->allowed('MANAGE_PROVIDER', $po->ordering_agency, $po);
-
- $li->provider($po->provider) unless defined $li->provider;
- }
-
- $li->selector($e->requestor->id);
- $e->create_acq_lineitem($li) or return $e->die_event;
-
- $e->commit;
- return $li->id;
-}
-
-
-__PACKAGE__->register_method(
- method => 'retrieve_lineitem',
- api_name => 'open-ils.acq.lineitem.retrieve',
- authoritative => 1,
- signature => {
- desc => 'Retrieves a lineitem',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'lineitem ID to retrieve', type => 'number'},
- {options => q/Hash of options, including:
-flesh_attrs : for attributes,
-flesh_notes : for notes,
-flesh_cancel_reason : for cancel reason,
-flesh_li_details : for order details objects,
-clear_marc : to clear marcxml from lineitem/, type => 'hash'},
- ],
- return => {desc => 'lineitem object on success, Event on error'}
- }
-);
-
-
-sub retrieve_lineitem {
- my($self, $conn, $auth, $li_id, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- return retrieve_lineitem_impl($e, $li_id, $options);
-}
-
-sub retrieve_lineitem_impl {
- my ($e, $li_id, $options, $no_auth) = @_; # no_auth needed for EDI scripts
- $options ||= {};
-
- my $flesh = {
- flesh => 3,
- flesh_fields => {
- jub => ['purchase_order', 'picklist'], # needed for permission check
- acqlid => [],
- acqlin => []
- }
- };
-
- my $fields = $flesh->{flesh_fields};
-
- push(@{$fields->{jub} }, 'attributes') if $$options{flesh_attrs};
- push(@{$fields->{jub} },'lineitem_notes') if $$options{flesh_notes};
- push(@{$fields->{acqlin}}, 'alert_text') if $$options{flesh_notes};
- push(@{$fields->{jub} }, 'order_summary') if $$options{flesh_order_summary};
- push(@{$fields->{acqlin}}, 'cancel_reason') if $$options{flesh_cancel_reason};
-
- if($$options{flesh_li_details}) {
- push(@{$fields->{jub} }, 'lineitem_details');
- push(@{$fields->{acqlid}}, 'fund' ) if $$options{flesh_fund};
- push(@{$fields->{acqlid}}, 'fund_debit' ) if $$options{flesh_fund_debit};
- push(@{$fields->{acqlid}}, 'cancel_reason') if $$options{flesh_cancel_reason};
- }
-
- if($$options{clear_marc}) { # avoid fetching marc blob
- my @fields = grep { $_ ne 'marc' } Fieldmapper::acq::lineitem->new->real_fields;
- $flesh->{select} = {jub => [@fields]};
- }
-
- my $li = $e->retrieve_acq_lineitem([$li_id, $flesh]) or return $e->event;
-
- # collect the # of lids
- if($$options{flesh_li_details}) {
- $li->item_count(scalar(@{$li->lineitem_details}));
- } else {
- my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
- $li->item_count(scalar(@$details));
- }
-
- # attach claims to LIDs
- if($$options{flesh_li_details}) {
- foreach (@{$li->lineitem_details}) {
- $_->claims(
- $e->search_acq_claim([
- {"lineitem_detail", $_->id}, {
- "flesh" => 1, "flesh_fields" => {"acqcl" => ["type"]}
- }
- ])
- );
- }
- }
-
- return $e->event unless ((
- $li->purchase_order and
- ($no_auth or $e->allowed(['VIEW_PURCHASE_ORDER', 'CREATE_PURCHASE_ORDER'],
- $li->purchase_order->ordering_agency, $li->purchase_order))
- ) or (
- $li->picklist and !$li->purchase_order and # user doesn't have view_po perms
- ($no_auth or $e->allowed(['VIEW_PICKLIST', 'CREATE_PICKLIST'],
- $li->picklist->org_unit, $li->picklist))
- ));
-
- unless ($$options{flesh_po}) {
- $li->purchase_order(
- $li->purchase_order ? $li->purchase_order->id : undef
- );
- }
- unless ($$options{flesh_pl}) {
- $li->picklist($li->picklist ? $li->picklist->id : undef);
- }
- return $li;
-}
-
-
-
-__PACKAGE__->register_method(
- method => 'delete_lineitem',
- api_name => 'open-ils.acq.lineitem.delete',
- signature => {
- desc => 'Deletes a lineitem',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'lineitem ID to delete', type => 'number'},
- ],
- return => {desc => '1 on success, Event on error'}
- }
-);
-
-__PACKAGE__->register_method(
- method => 'delete_lineitem',
- api_name => 'open-ils.acq.purchase_order.lineitem.delete',
- signature => {
- desc => 'Deletes a lineitem from a purchase order',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'lineitem ID to delete', type => 'number'},
- ],
- return => {desc => '1 on success, Event on error'}
- }
-);
-
-__PACKAGE__->register_method(
- method => 'delete_lineitem',
- api_name => 'open-ils.acq.picklist.lineitem.delete',
- signature => {
- desc => 'Deletes a lineitem from a picklist',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'lineitem ID to delete', type => 'number'},
- ],
- return => {desc => '1 on success, Event on error'}
- }
-);
-
-sub delete_lineitem {
- my($self, $conn, $auth, $li_id) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- my $li = $e->retrieve_acq_lineitem($li_id)
- or return $e->die_event;
-
- # XXX check state
-
- if($li->picklist) {
- my $picklist = $e->retrieve_acq_picklist($li->picklist)
- or return $e->die_event;
- return OpenILS::Event->new('BAD_PARAMS')
- if $picklist->owner != $e->requestor->id;
- } else {
- # check PO perms
- }
-
- # once a LI is attached to a PO, deleting it
- # from a picklist means *detaching* it from the picklist
- if ($self->api_name =~ /picklist/ && $li->purchase_order) {
- $li->clear_picklist;
- my $evt = update_lineitem_impl($e, $li);
- return $evt if $evt;
- $e->commit;
- return 1;
- }
-
- # delete the attached lineitem_details
- my $lid_ids = $e->search_acq_lineitem_detail(
- {lineitem => $li_id}, {idlist=>1});
-
- for my $lid_id (@$lid_ids) {
- $e->delete_acq_lineitem_detail(
- $e->retrieve_acq_lineitem_detail($lid_id))
- or return $e->die_event;
- }
-
- $e->delete_acq_lineitem($li) or return $e->die_event;
- $e->commit;
- return 1;
-}
-
-
-__PACKAGE__->register_method(
- method => 'update_lineitem',
- api_name => 'open-ils.acq.lineitem.update',
- signature => {
- desc => 'Update one or many lineitems',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'lineitem object update', type => 'object'}
- ],
- return => {desc => '1 on success, Event on error'}
- }
-);
-
-sub update_lineitem {
- my($self, $conn, $auth, $li) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- $li = [$li] unless ref $li eq "ARRAY";
- foreach (@$li) {
- my $evt = update_lineitem_impl($e, $_);
- return $evt if $evt;
- }
-
- $e->commit;
- return 1;
-}
-
-sub update_lineitem_impl {
- my($e, $li) = @_;
-
- my $orig_li = $e->retrieve_acq_lineitem([
- $li->id,
- { flesh => 1, # grab the lineitem with picklist attached
- flesh_fields => {jub => ['picklist', 'purchase_order']}
- }
- ]) or return $e->die_event;
-
- # the marc may have been cleared on retrieval...
- $li->marc($orig_li->marc) unless $li->marc;
-
- $li->editor($e->requestor->id);
- $li->edit_time('now');
- $e->update_acq_lineitem($li) or return $e->die_event;
- return undef;
-}
-
-__PACKAGE__->register_method(
- method => 'lineitem_search',
- api_name => 'open-ils.acq.lineitem.search',
- stream => 1,
- signature => {
- desc => 'Searches lineitems',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'Search definition', type => 'object'},
- {desc => 'Options hash. idlist=true', type => 'object'},
- {desc => 'List of lineitems', type => 'object/number'},
- ]
- }
-);
-
-sub lineitem_search {
- my($self, $conn, $auth, $search, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- return $e->event unless $e->allowed('CREATE_PICKLIST');
- # XXX needs permissions consideration
- my $lis = $e->search_acq_lineitem($search, {idlist=>1});
- for my $li_id (@$lis) {
- if($$options{idlist}) {
- $conn->respond($li_id);
- } else {
- my $res = retrieve_lineitem($self, $conn, $auth, $li_id, $options);
- $conn->respond($res) unless $U->event_code($res);
- }
- }
- return undef;
-}
-
-__PACKAGE__->register_method (
- method => 'lineitems_related_by_bib',
- api_name => 'open-ils.acq.lineitems_for_bib.by_bib_id',
- stream => 1,
- signature => q/
- Retrieves lineitems attached to same bib record, subject to the PO ordering agency. This variant takes the bib id.
- @param authtoken Login session key
- @param bib_id Id for the pertinent bib record.
- @param options Object for tweaking the selection criteria and fleshing options.
- /
-);
-
-__PACKAGE__->register_method (
- method => 'lineitems_related_by_bib',
- api_name => 'open-ils.acq.lineitems_for_bib.by_lineitem_id',
- stream => 1,
- signature => q/
- Retrieves lineitems attached to same bib record, subject to the PO ordering agency. This variant takes the id for any of the pertinent lineitems.
- @param authtoken Login session key
- @param bib_id Id for a pertinent lineitem.
- @param options Object for tweaking the selection criteria and fleshing options.
- /
-);
-
-__PACKAGE__->register_method (
- method => 'lineitems_related_by_bib',
- api_name => 'open-ils.acq.lineitems_for_bib.by_lineitem_id.count',
- stream => 1,
- signature => q/See open-ils.acq.lineitems_for_bib.by_lineitem_id. This version returns numbers of lineitems only (XXX may count lineitems we don't actually have permission to retrieve)/
-);
-
-sub lineitems_related_by_bib {
- my($self, $conn, $auth, $test_value, $options) = @_;
- my $e = new_editor(authtoken => $auth);
- return $e->event unless $e->checkauth;
-
- my $perm_orgs = $U->user_has_work_perm_at($e, 'VIEW_PURCHASE_ORDER', {descendants =>1}, $e->requestor->id);
-
- my $query = {
- "select"=>{"jub"=>["id"]},
- "from"=>{"jub" => {"acqpo" => {type => 'left'}, "acqpl" => {type => 'left'}}},
- "where"=>{
- '-or' => [
- { "+acqpo"=>{ "ordering_agency" => $perm_orgs } },
- { '+acqpl' => { org_unit => $perm_orgs } }
- ]
- },
- "order_by"=>[{"class"=>"jub", "field"=>"create_time", "direction"=>"desc"}]
- };
-
- # Be sure we just return the original LI if no related bibs
- if ($self->api_name =~ /by_lineitem_id/) {
- my $orig = retrieve_lineitem($self, $conn, $auth, $test_value) or
- return $e->die_event;
- if ($test_value = $orig->eg_bib_id) {
- $query->{"where"}->{"eg_bib_id"} = $test_value;
- } else {
- $query->{"where"}->{"id"} = $orig->id;
- }
- } elsif ($test_value) {
- $query->{"where"}->{"eg_bib_id"} = $test_value;
- } else {
- $e->disconnect;
- return new OpenILS::Event("BAD_PARAMS", "Null bib id");
- }
-
- if ($options && defined $options->{lineitem_state}) {
- $query->{'where'}{'jub'}{'state'} = $options->{lineitem_state};
- }
-
- if ($options && defined $options->{po_state}) {
- $query->{'where'}{'+acqpo'}{'state'} = $options->{po_state};
- }
-
- if ($options && defined $options->{order_by}) {
- $query->{'order_by'} = $options->{order_by};
- }
-
- my $results = $e->json_query($query);
- if ($self->api_name =~ /count$/) {
- return scalar(@$results);
- } else {
- for my $result (@$results) {
- # retrieve_lineitem takes care of POs and PLs and also handles
- # options like flesh_notes and permissions checking.
- $conn->respond(
- retrieve_lineitem($self, $conn, $auth, $result->{"id"}, $options)
- );
- }
- }
-
- return undef;
-}
-
-
-__PACKAGE__->register_method(
- method => "lineitem_search_by_attributes",
- api_name => "open-ils.acq.lineitem.search.by_attributes",
- stream => 1,
- signature => {
- desc => "Performs a search against lineitem_attrs",
- params => [
- {desc => "Authentication token", type => "string"},
- { desc => q/
-Search definition:
- attr_value_pairs : list of pairs of (attr definition ID, attr value) where value can be scalar (fuzzy match) or array (exact match)
- li_states : list of lineitem states
- po_agencies : list of purchase order ordering agencies (org) ids
-
-At least one of these search terms is required.
- /,
- type => "object"},
- { desc => q/
-Options hash:
- idlist : if set, only return lineitem IDs
- clear_marc : if set, strip the MARC xml from the lineitem before delivery
- flesh_attrs : flesh lineitem attributes;
- /,
- type => "object"}
- ]
- }
-);
-
-__PACKAGE__->register_method(
- method => "lineitem_search_by_attributes",
- api_name => "open-ils.acq.lineitem.search.by_attributes.ident",
- stream => 1,
- signature => {
- desc => "Performs a search against lineitem_attrs where ident is true. ".
- "See open-ils.acq.lineitem.search.by_attributes for params."
- }
-);
-
-sub lineitem_search_by_attributes {
- my ($self, $conn, $auth, $search, $options) = @_;
-
- my $e = new_editor(authtoken => $auth, xact => 1);
- return $e->die_event unless $e->checkauth;
- # XXX needs permissions consideration
-
- return [] unless $search;
- my $attr_value_pairs = $search->{attr_value_pairs};
- my $li_states = $search->{li_states};
- my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
-
- my $query = {
- "select" => {"acqlia" =>
- [{"column" => "lineitem", "transform" => "distinct"}]
- },
- "from" => {
- "acqlia" => {
- "acqliad" => {"field" => "id", "fkey" => "definition"},
- "jub" => {
- "field" => "id",
- "fkey" => "lineitem",
- "join" => {
- "acqpo" => {
- "type" => "left",
- "field" => "id",
- "fkey" => "purchase_order"
- }
- }
- }
- }
- }
- };
-
- my $where = {};
- $where->{"+acqliad"} = {"ident" => "t"}
- if $self->api_name =~ /\.ident/;
-
- my $searched_for_something = 0;
-
- if (ref $attr_value_pairs eq "ARRAY") {
- $where->{"-or"} = [];
- foreach (@$attr_value_pairs) {
- next if @$_ != 2;
- my ($def, $value) = @$_;
- push @{$where->{"-or"}}, {
- "-and" => {
- "attr_value" => (ref $value) ?
- $value : {"ilike" => "%" . $value . "%"},
- "definition" => $def
- }
- };
- }
- $searched_for_something = 1;
- }
-
- if ($li_states and @$li_states) {
- $where->{"+jub"} = {"state" => $li_states};
- $searched_for_something = 1;
- }
-
- if ($po_agencies and @$po_agencies) {
- $where->{"+acqpo"} = {"ordering_agency" => $po_agencies};
- $searched_for_something = 1;
- }
-
- if (not $searched_for_something) {
- $e->rollback;
- return new OpenILS::Event(
- "BAD_PARAMS", note => "You have provided no search terms."
- );
- }
-
- $query->{"where"} = $where;
- my $lis = $e->json_query($query);
-
- for my $li_id_obj (@$lis) {
- my $li_id = $li_id_obj->{"lineitem"};
- if($options->{"idlist"}) {
- $conn->respond($li_id);
- } else {
- $conn->respond(
- retrieve_lineitem($self, $conn, $auth, $li_id, $options)
- );
- }
- }
- undef;
-}
-
-
-__PACKAGE__->register_method(
- method => 'lineitem_search_ident',
- api_name => 'open-ils.acq.lineitem.search.ident',
- stream => 1,
- signature => {
- desc => 'Performs a search against lineitem_attrs where ident is true',
- params => [
- {desc => 'Authentication token', type => 'string'},
- { desc => q/Search definition. Options are:
- attr_values : list of attribute values (required)
- li_states : list of lineitem states
- po_agencies : list of purchase order ordering agencies (org) ids
- /,
- type => 'object',
- },
- { desc => q/
- Options hash. Options are:
- idlist : if set, only return lineitem IDs
- clear_marc : if set, strip the MARC xml from the lineitem before delivery
- flesh_attrs : flesh lineitem attributes;
- /,
- type => 'object',
- }
- ]
- }
-);
-
-my $LI_ATTR_SEARCH = {
- select => { acqlia => ['lineitem'] },
- from => {
- acqlia => {
- acqliad => {
- field => 'id',
- fkey => 'definition'
- },
- jub => {
- field => 'id',
- fkey => 'lineitem',
- join => {
- acqpo => {
- field => 'id',
- fkey => 'purchase_order'
- }
- }
- }
- }
- }
-};
-
-sub lineitem_search_ident {
- my($self, $conn, $auth, $search, $options) = @_;
- my $e = new_editor(authtoken=>$auth, xact=>1);
- return $e->event unless $e->checkauth;
- # XXX needs permissions consideration
-
- return [] unless $search;
- my $attr_values = $search->{attr_values};
- my $li_states = $search->{li_states};
- my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
-
- my $where_clause = {
- '-or' => [],
- '+acqlia' => {
- '+acqliad' => {ident => 't'},
- }
- };
-
- push(@{$where_clause->{'-or'}}, {attr_value => {ilike => "%$_%"}}) for @$attr_values;
-
- $where_clause->{'+jub'} = {state => {in => $li_states}}
- if $li_states and @$li_states;
-
- $where_clause->{'+acqpo'} = {ordering_agency => $po_agencies}
- if $po_agencies and @$po_agencies;
-
- $LI_ATTR_SEARCH->{where} = $where_clause;
-
- my $lis = $e->json_query($LI_ATTR_SEARCH);
-
- for my $li_id_obj (@$lis) {
- my $li_id = $li_id_obj->{lineitem};
- if($$options{idlist}) {
- $conn->respond($li_id);
- } else {
- my $li;
- if($$options{flesh_attrs}) {
- $li = $e->retrieve_acq_lineitem([
- $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
- } else {
- $li = $e->retrieve_acq_lineitem($li_id);
- }
- $li->clear_marc if $$options{clear_marc};
- $conn->respond($li);
- }
- }
- return undef;
-}
-
-
-__PACKAGE__->register_method(
- method => 'retrieve_lineitem_detail',
- api_name => 'open-ils.acq.lineitem_detail.retrieve',
- authoritative => 1,
- signature => {
- desc => q/Updates a lineitem detail/,
- params => [
- { desc => 'Authentication token', type => 'string' },
- { desc => 'id of lineitem_detail to retrieve', type => 'number' },
- ],
- return => { desc => 'object on success, Event on failure' }
- }
-);
-sub retrieve_lineitem_detail {
- my($self, $conn, $auth, $li_detail_id) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
-
- my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
- or return $e->event;
-
- if($li_detail->fund) {
- my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->event;
- return $e->event unless
- $e->allowed('MANAGE_FUND', $fund->org, $fund);
- }
-
- # XXX check lineitem perms
- return $li_detail;
-}
-
-
-__PACKAGE__->register_method(
- method => 'approve_lineitem',
- api_name => 'open-ils.acq.lineitem.approve',
- signature => {
- desc => 'Mark a lineitem as approved',
- params => [
- { desc => 'Authentication token', type => 'string' },
- { desc => 'lineitem ID', type => 'number' }
- ],
- return => { desc => '1 on success, Event on error' }
- }
-);
-sub approve_lineitem {
- my($self, $conn, $auth, $li_id) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- # XXX perm checks for each lineitem detail
-
- my $li = $e->retrieve_acq_lineitem($li_id)
- or return $e->die_event;
-
- return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li_id)
- if $li->state eq 'approved';
-
- my $details = $e->search_acq_lineitem_detail({lineitem => $li_id});
- return OpenILS::Event->new('ACQ_LINEITEM_NO_COPIES', payload => $li_id)
- unless scalar(@$details) > 0;
-
- for my $detail (@$details) {
- return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_FUND', payload => $detail->id)
- unless $detail->fund;
-
- return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_ORG', payload => $detail->id)
- unless $detail->owning_lib;
- }
-
- $li->state('approved');
- $li->edit_time('now');
- $e->update_acq_lineitem($li) or return $e->die_event;
-
- $e->commit;
- return 1;
-}
-
-
-
-__PACKAGE__->register_method(
- method => 'set_lineitem_attr',
- api_name => 'open-ils.acq.lineitem_usr_attr.set',
- signature => {
- desc => 'Sets a lineitem_usr_attr value',
- params => [
- { desc => 'Authentication token', type => 'string' },
- { desc => 'Lineitem ID', type => 'number' },
- { desc => 'Attr name', type => 'string' },
- { desc => 'Attr value', type => 'string' }
- ],
- return => { desc => '1 on success, Event on error' }
- }
-);
-
-__PACKAGE__->register_method(
- method => 'set_lineitem_attr',
- api_name => 'open-ils.acq.lineitem_local_attr.set',
- signature => {
- desc => 'Sets a lineitem_local_attr value',
- params => [
- { desc => 'Authentication token', type => 'string' },
- { desc => 'Lineitem ID', type => 'number' },
- { desc => 'Attr name', type => 'string' },
- { desc => 'Attr value', type => 'string' }
- ],
- return => { desc => 'ID of the attr object on success, Event on error' }
- }
-);
-
-
-sub set_lineitem_attr {
- my($self, $conn, $auth, $li_id, $attr_name, $attr_value) = @_;
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
-
- # XXX perm
-
- my $attr_type = $self->api_name =~ /local_attr/ ?
- 'lineitem_local_attr_definition' : 'lineitem_usr_attr_definition';
-
- my $attr = $e->search_acq_lineitem_attr({
- lineitem => $li_id,
- attr_type => $attr_type,
- attr_name => $attr_name})->[0];
-
- my $find = "search_acq_$attr_type";
-
- if($attr) {
- $attr->attr_value($attr_value);
- $e->update_acq_lineitem_attr($attr) or return $e->die_event;
- } else {
- $attr = Fieldmapper::acq::lineitem_attr->new;
- $attr->lineitem($li_id);
- $attr->attr_type($attr_type);
- $attr->attr_name($attr_name);
- $attr->attr_value($attr_value);
-
- my $attr_def_id = $e->$find({code => $attr_name}, {idlist=>1})->[0]
- or return $e->die_event;
- $attr->definition($attr_def_id);
- $e->create_acq_lineitem_attr($attr) or return $e->die_event;
- }
-
- $e->commit;
- return $attr->id;
-}
-
-__PACKAGE__->register_method(
- method => 'get_lineitem_attr_defs',
- api_name => 'open-ils.acq.lineitem_attr_definition.retrieve.all',
- authoritative => 1,
- signature => {
- desc => 'Retrieve lineitem attr definitions',
- params => [ { desc => 'Authentication token', type => 'string' }, ],
- return => { desc => 'List of attr definitions' }
- }
-);
-
-sub get_lineitem_attr_defs {
- my($self, $conn, $auth) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- my %results;
- for my $type (qw/generated marc local usr provider/) {
- my $call = "retrieve_all_acq_lineitem_${type}_attr_definition";
- $results{$type} = $e->$call;
- }
- return \%results;
-}
-
-
-__PACKAGE__->register_method(
- method => 'lineitem_note_CUD_batch',
- api_name => 'open-ils.acq.lineitem_note.cud.batch',
- stream => 1,
- signature => {
- desc => q/Manage lineitem notes/,
- params => [
- { desc => 'Authentication token', type => 'string' },
- { desc => 'List of lineitem_notes to manage', type => 'array' },
- ],
- return =>
- { desc => 'Streaming response of current position in the array' }
- }
-);
-
-sub lineitem_note_CUD_batch {
- my($self, $conn, $auth, $li_notes) = @_;
-
- my $e = new_editor(xact=>1, authtoken=>$auth);
- return $e->die_event unless $e->checkauth;
- # XXX perms
-
- my $total = @$li_notes;
- my $count = 0;
-
- for my $note (@$li_notes) {
-
- $note->editor($e->requestor->id);
- $note->edit_time('now');
-
- if($note->isnew) {
- $note->creator($e->requestor->id);
- $note = $e->create_acq_lineitem_note($note) or return $e->die_event;
-
- } elsif($note->isdeleted) {
- $e->delete_acq_lineitem_note($note) or return $e->die_event;
-
- } elsif($note->ischanged) {
- $e->update_acq_lineitem_note($note) or return $e->die_event;
- }
-
- if(!$note->isdeleted) {
- $note = $e->retrieve_acq_lineitem_note([
- $note->id, {
- "flesh" => 1, "flesh_fields" => {"acqlin" => ["alert_text"]}
- }
- ]);
- }
-
- $conn->respond({maximum => $total, progress => ++$count, note => $note});
- }
-
- $e->commit;
- return {complete => 1};
-}
-
-__PACKAGE__->register_method(
- method => 'ranged_line_item_alert_text',
- api_name => 'open-ils.acq.line_item_alert_text.ranged.retrieve.all'); # TODO: signature
-
-sub ranged_line_item_alert_text {
- my($self, $conn, $auth, $org_id, $depth) = @_;
- my $e = new_editor(authtoken => $auth);
- return $e->event unless $e->checkauth;
- return $e->event unless $e->allowed('ADMIN_ACQ_LINEITEM_ALERT_TEXT', $org_id);
- return $e->search_acq_lineitem_alert_text(
- {owning_lib => $U->get_org_full_path($org_id, $depth)});
-}
-
-
-__PACKAGE__->register_method(
- method => "retrieve_lineitem_by_copy_id",
- api_name => "open-ils.acq.lineitem.retrieve.by_copy_id",
- authoritative => 1,
- signature => {
- desc => q/Manage lineitem notes/,
- params => [
- {desc => "Authentication token", type => "string"},
- {desc => "Evergreen internal copy ID", type => "number"},
- {desc => "Hash of options (see open-ils.acq.lineitem.retrieve",
- type => "object"}
- ],
- return => {
- desc => "Lineitem associated with given copy",
- type => "object", class => "jub"
- }
- }
-);
-
-sub retrieve_lineitem_by_copy_id {
- my ($self, $conn, $auth, $object_id, $options) = @_;
-
- my $e = new_editor("authtoken" => $auth);
- return $e->die_event unless $e->checkauth;
-
- my $result = $e->json_query({
- "select" => {"acqlid" => ["lineitem"]},
- "from" => "acqlid",
- "where" => {"eg_copy_id" => $object_id}
- })->[0] or do {
- $e->disconnect;
- return new OpenILS::Event("ACQ_LINEITEM_NOT_FOUND");
- };
-
- my $li = retrieve_lineitem_impl($e, $result->{"lineitem"}, $options) or
- return $e->die_event;
-
- $e->disconnect;
- return $li;
-}
-
-1;
+++ /dev/null
-package OpenILS::Application::Acq::BatchManager;
-use OpenILS::Application::Acq::Financials;
-use OpenSRF::AppSession;
-use OpenSRF::EX qw/:try/;
-use strict; use warnings;
-
-sub new {
- my($class, %args) = @_;
- my $self = bless(\%args, $class);
- $self->{args} = {
- lid => 0,
- li => 0,
- copies => 0,
- bibs => 0,
- progress => 0,
- debits_accrued => 0,
- purchase_order => undef,
- picklist => undef,
- complete => 0,
- indexed => 0,
- total => 0
- };
- $self->{ingest_queue} = [];
- $self->{cache} = {};
- $self->throttle(5) unless $self->throttle;
- $self->{post_proc_queue} = [];
- $self->{last_respond_progress} = 0;
- return $self;
-}
-
-sub conn {
- my($self, $val) = @_;
- $self->{conn} = $val if $val;
- return $self->{conn};
-}
-sub throttle {
- my($self, $val) = @_;
- $self->{throttle} = $val if $val;
- return $self->{throttle};
-}
-sub respond {
- my($self, %other_args) = @_;
- if($self->throttle and not %other_args) {
- return unless (
- ($self->{args}->{progress} - $self->{last_respond_progress}) >= $self->throttle
- );
- }
- $self->conn->respond({ %{$self->{args}}, %other_args });
- $self->{last_respond_progress} = $self->{args}->{progress};
-}
-sub respond_complete {
- my($self, %other_args) = @_;
- $self->complete;
- $self->conn->respond_complete({ %{$self->{args}}, %other_args });
- $self->run_post_response_hooks;
- return undef;
-}
-
-# run the post response hook subs, shifting them off as we go
-sub run_post_response_hooks {
- my($self) = @_;
- (shift @{$self->{post_proc_queue}})->() while @{$self->{post_proc_queue}};
-}
-
-# any subs passed to this method will be run after the call to respond_complete
-sub post_process {
- my($self, $sub) = @_;
- push(@{$self->{post_proc_queue}}, $sub);
-}
-
-sub total {
- my($self, $val) = @_;
- $self->{args}->{total} = $val if defined $val;
- $self->{args}->{maximum} = $self->{args}->{total};
- return $self->{args}->{total};
-}
-sub purchase_order {
- my($self, $val) = @_;
- $self->{args}->{purchase_order} = $val if $val;
- return $self;
-}
-sub picklist {
- my($self, $val) = @_;
- $self->{args}->{picklist} = $val if $val;
- return $self;
-}
-sub add_lid {
- my $self = shift;
- $self->{args}->{lid} += 1;
- $self->{args}->{progress} += 1;
- return $self;
-}
-sub add_li {
- my $self = shift;
- $self->{args}->{li} += 1;
- $self->{args}->{progress} += 1;
- return $self;
-}
-sub add_copy {
- my $self = shift;
- $self->{args}->{copies} += 1;
- $self->{args}->{progress} += 1;
- return $self;
-}
-sub add_bib {
- my $self = shift;
- $self->{args}->{bibs} += 1;
- $self->{args}->{progress} += 1;
- return $self;
-}
-sub add_debit {
- my($self, $amount) = @_;
- $self->{args}->{debits_accrued} += $amount;
- $self->{args}->{progress} += 1;
- return $self;
-}
-sub editor {
- my($self, $editor) = @_;
- $self->{editor} = $editor if defined $editor;
- return $self->{editor};
-}
-sub complete {
- my $self = shift;
- $self->{args}->{complete} = 1;
- return $self;
-}
-
-sub ingest_ses {
- my($self, $val) = @_;
- $self->{ingest_ses} = $val if $val;
- return $self->{ingest_ses};
-}
-
-sub push_ingest_queue {
- my($self, $rec_id) = @_;
-
- $self->ingest_ses(OpenSRF::AppSession->connect('open-ils.ingest'))
- unless $self->ingest_ses;
-
- my $req = $self->ingest_ses->request('open-ils.ingest.full.biblio.record', $rec_id);
-
- push(@{$self->{ingest_queue}}, $req);
-}
-
-sub process_ingest_records {
- my $self = shift;
- return unless @{$self->{ingest_queue}};
-
- for my $req (@{$self->{ingest_queue}}) {
-
- try {
- $req->gather(1);
- $self->{args}->{indexed} += 1;
- $self->{args}->{progress} += 1;
- } otherwise {};
-
- $self->respond;
- }
- $self->ingest_ses->disconnect;
-}
-
-
-sub cache {
- my($self, $org, $key, $val) = @_;
- $self->{cache}->{$org} = {} unless $self->{cache}->{org};
- $self->{cache}->{$org}->{$key} = $val if defined $val;
- return $self->{cache}->{$org}->{$key};
-}
-
-
-package OpenILS::Application::Acq::Order;
-use base qw/OpenILS::Application/;
-use strict; use warnings;
-# ----------------------------------------------------------------------------
-# Break up each component of the order process and pieces into managable
-# actions that can be shared across different workflows
-# ----------------------------------------------------------------------------
-use OpenILS::Event;
-use OpenSRF::Utils::Logger qw(:logger);
-use OpenSRF::Utils::JSON;
-use OpenSRF::AppSession;
-use OpenILS::Utils::Fieldmapper;
-use OpenILS::Utils::CStoreEditor q/:funcs/;
-use OpenILS::Const qw/:const/;
-use OpenSRF::EX q/:try/;
-use OpenILS::Application::AppUtils;
-use OpenILS::Application::Cat::BibCommon;
-use OpenILS::Application::Cat::AssetCommon;
-use MARC::Record;
-use MARC::Batch;
-use MARC::File::XML;
-my $U = 'OpenILS::Application::AppUtils';
-
-
-# ----------------------------------------------------------------------------
-# Lineitem
-# ----------------------------------------------------------------------------
-sub create_lineitem {
- my($mgr, %args) = @_;
- my $li = Fieldmapper::acq::lineitem->new;
- $li->creator($mgr->editor->requestor->id);
- $li->selector($li->creator);
- $li->editor($li->creator);
- $li->create_time('now');
- $li->edit_time('now');
- $li->state('new');
- $li->$_($args{$_}) for keys %args;
- $li->clear_id;
- $mgr->add_li;
- $mgr->editor->create_acq_lineitem($li) or return 0;
-
- unless($li->estimated_unit_price) {
- # extract the price from the MARC data
- my $price = get_li_price_from_attr($mgr->editor, $li) or return $li;
- $li->estimated_unit_price($price);
- return update_lineitem($mgr, $li);
- }
-
- return $li;
-}
-
-sub get_li_price_from_attr {
- my($e, $li) = @_;
- my $attrs = $li->attributes || $e->search_acq_lineitem_attr({lineitem => $li->id});
-
- for my $attr_type (qw/
- lineitem_local_attr_definition
- lineitem_prov_attr_definition
- lineitem_marc_attr_definition/) {
-
- my ($attr) = grep {
- $_->attr_name eq 'estimated_price' and
- $_->attr_type eq $attr_type } @$attrs;
-
- return $attr->attr_value if $attr;
- }
-
- return undef;
-}
-
-
-sub update_lineitem {
- my($mgr, $li) = @_;
- $li->edit_time('now');
- $li->editor($mgr->editor->requestor->id);
- $mgr->add_li;
- return $mgr->editor->retrieve_acq_lineitem($mgr->editor->data) if
- $mgr->editor->update_acq_lineitem($li);
- return undef;
-}
-
-
-# ----------------------------------------------------------------------------
-# Create real holds from patron requests for a given lineitem
-# ----------------------------------------------------------------------------
-sub promote_lineitem_holds {
- my($mgr, $li) = @_;
-
- my $requests = $mgr->editor->search_acq_user_request(
- { lineitem => $li->id,
- '-or' =>
- [ { need_before => {'>' => 'now'} },
- { need_before => undef }
- ]
- }
- );
-
- for my $request ( @$requests ) {
-
- $request->eg_bib( $li->eg_bib_id );
- $mgr->editor->update_acq_user_request( $request ) or return 0;
-
- next unless ($U->is_true( $request->hold ));
-
- my $hold = Fieldmapper::action::hold_request->new;
- $hold->usr( $request->usr );
- $hold->requestor( $request->usr );
- $hold->request_time( $request->request_date );
- $hold->pickup_lib( $request->pickup_lib );
- $hold->request_lib( $request->pickup_lib );
- $hold->selection_ou( $request->pickup_lib );
- $hold->phone_notify( $request->phone_notify );
- $hold->email_notify( $request->email_notify );
- $hold->expire_time( $request->need_before );
-
- if ($request->holdable_formats) {
- my $mrm = $mgr->editor->search_metabib_metarecord_source_map( { source => $li->eg_bib_id } )->[0];
- if ($mrm) {
- $hold->hold_type( 'M' );
- $hold->holdable_formats( $request->holdable_formats );
- $hold->target( $mrm->metarecord );
- }
- }
-
- if (!$hold->target) {
- $hold->hold_type( 'T' );
- $hold->target( $li->eg_bib_id );
- }
-
- $mgr->editor->create_actor_hold_request( $hold ) or return 0;
- }
-
- return $li;
-}
-
-sub delete_lineitem {
- my($mgr, $li) = @_;
- $li = $mgr->editor->retrieve_acq_lineitem($li) unless ref $li;
-
- # delete the attached lineitem_details
- my $lid_ids = $mgr->editor->search_acq_lineitem_detail({lineitem => $li->id}, {idlist=>1});
- for my $lid_id (@$lid_ids) {
- return 0 unless delete_lineitem_detail($mgr, $lid_id);
- }
-
- $mgr->add_li;
- return $mgr->editor->delete_acq_lineitem($li);
-}
-
-# begins and commit transactions as it goes
-sub create_lineitem_list_assets {
- my($mgr, $li_ids) = @_;
- return undef if check_import_li_marc_perms($mgr, $li_ids);
-
- # create the bibs/volumes/copies and ingest the records
- for my $li_id (@$li_ids) {
- $mgr->editor->xact_begin;
- my $data = create_lineitem_assets($mgr, $li_id) or return undef;
- $mgr->editor->xact_commit;
- # XXX ingest is in-db now
- #$mgr->push_ingest_queue($data->{li}->eg_bib_id) if $data->{new_bib};
- $mgr->respond;
- }
- $mgr->process_ingest_records;
- return 1;
-}
-
-# returns event on error, undef on success
-sub check_import_li_marc_perms {
- my($mgr, $li_ids) = @_;
-
- # if there are any order records that are not linked to
- # in-db bib records, verify staff has perms to import order records
- my $order_li = $mgr->editor->search_acq_lineitem(
- [{id => $li_ids, eg_bib_id => undef}, {limit => 1}], {idlist => 1})->[0];
-
- if($order_li) {
- return $mgr->editor->die_event unless
- $mgr->editor->allowed('IMPORT_ACQ_LINEITEM_BIB_RECORD');
- }
-
- return undef;
-}
-
-
-# ----------------------------------------------------------------------------
-# if all of the lineitem details for this lineitem have
-# been received, mark the lineitem as received
-# returns 1 on non-received, li on received, 0 on error
-# ----------------------------------------------------------------------------
-
-sub describe_affected_po {
- my ($e, $po) = @_;
-
- my ($enc, $spent) =
- OpenILS::Application::Acq::Financials::build_price_summary(
- $e, $po->id
- );
-
- +{$po->id => {
- "state" => $po->state,
- "amount_encumbered" => $enc,
- "amount_spent" => $spent
- }
- };
-}
-
-sub check_lineitem_received {
- my($mgr, $li_id) = @_;
-
- my $non_recv = $mgr->editor->search_acq_lineitem_detail(
- {recv_time => undef, lineitem => $li_id}, {idlist=>1});
-
- return 1 if @$non_recv;
-
- my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
- $li->state('received');
- return update_lineitem($mgr, $li);
-}
-
-sub receive_lineitem {
- my($mgr, $li_id, $skip_complete_check) = @_;
- my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
-
- my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
- {lineitem => $li_id, recv_time => undef}, {idlist => 1});
-
- for my $lid_id (@$lid_ids) {
- receive_lineitem_detail($mgr, $lid_id, 1) or return 0;
- }
-
- $mgr->add_li;
- $li->state('received');
-
- $li = update_lineitem($mgr, $li) or return 0;
- $mgr->post_process( sub { create_lineitem_status_events($mgr, $li_id, 'aur.received'); });
-
- my $po;
- return 0 unless
- $skip_complete_check or (
- $po = check_purchase_order_received($mgr, $li->purchase_order)
- );
-
- my $result = {"li" => {$li->id => {"state" => $li->state}}};
- $result->{"po"} = describe_affected_po($mgr->editor, $po) if ref $po;
- return $result;
-}
-
-sub rollback_receive_lineitem {
- my($mgr, $li_id) = @_;
- my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
-
- my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
- {lineitem => $li_id, recv_time => {'!=' => undef}}, {idlist => 1});
-
- for my $lid_id (@$lid_ids) {
- rollback_receive_lineitem_detail($mgr, $lid_id, 1) or return 0;
- }
-
- $mgr->add_li;
- $li->state('on-order');
- return update_lineitem($mgr, $li);
-}
-
-
-sub create_lineitem_status_events {
- my($mgr, $li_id, $hook) = @_;
-
- my $ses = OpenSRF::AppSession->create('open-ils.trigger');
- $ses->connect;
- my $user_reqs = $mgr->editor->search_acq_user_request([
- {lineitem => $li_id},
- {flesh => 1, flesh_fields => {aur => ['usr']}}
- ]);
-
- for my $user_req (@$user_reqs) {
- my $req = $ses->request('open-ils.trigger.event.autocreate', $hook, $user_req, $user_req->usr->home_ou);
- $req->recv;
- }
-
- $ses->disconnect;
- return undef;
-}
-
-# ----------------------------------------------------------------------------
-# Lineitem Detail
-# ----------------------------------------------------------------------------
-sub create_lineitem_detail {
- my($mgr, %args) = @_;
- my $lid = Fieldmapper::acq::lineitem_detail->new;
- $lid->$_($args{$_}) for keys %args;
- $lid->clear_id;
- $mgr->add_lid;
- return $mgr->editor->create_acq_lineitem_detail($lid);
-}
-
-
-# flesh out any required data with default values where appropriate
-sub complete_lineitem_detail {
- my($mgr, $lid) = @_;
- unless($lid->barcode) {
- my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
- $lid->barcode($pfx.$lid->id);
- }
-
- unless($lid->cn_label) {
- my $pfx = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
- $lid->cn_label($pfx.$lid->id);
- }
-
- if(!$lid->location and my $loc = $U->ou_ancestor_setting_value($lid->owning_lib, 'acq.default_copy_location')) {
- $lid->location($loc);
- }
-
- $lid->circ_modifier(get_default_circ_modifier($mgr, $lid->owning_lib))
- unless defined $lid->circ_modifier;
-
- $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
- return $lid;
-}
-
-sub get_default_circ_modifier {
- my($mgr, $org) = @_;
- my $code = $mgr->cache($org, 'def_circ_mod');
- $code = $U->ou_ancestor_setting_value($org, 'acq.default_circ_modifier') unless defined $code;
- return $mgr->cache($org, 'def_circ_mod', $code) if defined $code;
- return undef;
-}
-
-sub delete_lineitem_detail {
- my($mgr, $lid) = @_;
- $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid) unless ref $lid;
- return $mgr->editor->delete_acq_lineitem_detail($lid);
-}
-
-
-sub receive_lineitem_detail {
- my($mgr, $lid_id, $skip_complete_check) = @_;
- my $e = $mgr->editor;
-
- my $lid = $e->retrieve_acq_lineitem_detail([
- $lid_id,
- { flesh => 1,
- flesh_fields => {
- acqlid => ['fund_debit']
- }
- }
- ]) or return 0;
-
- return 1 if $lid->recv_time;
-
- $lid->recv_time('now');
- $e->update_acq_lineitem_detail($lid) or return 0;
-
- my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
- $copy->status(OILS_COPY_STATUS_IN_PROCESS);
- $copy->edit_date('now');
- $copy->editor($e->requestor->id);
- $e->update_asset_copy($copy) or return 0;
-
- $mgr->add_lid;
-
- return 1 if $skip_complete_check;
-
- my $li = check_lineitem_received($mgr, $lid->lineitem) or return 0;
- return 1 if $li == 1; # li not received
-
- return check_purchase_order_received($mgr, $li->purchase_order) or return 0;
-}
-
-
-sub rollback_receive_lineitem_detail {
- my($mgr, $lid_id) = @_;
- my $e = $mgr->editor;
-
- my $lid = $e->retrieve_acq_lineitem_detail([
- $lid_id,
- { flesh => 1,
- flesh_fields => {
- acqlid => ['fund_debit']
- }
- }
- ]) or return 0;
-
- return 1 unless $lid->recv_time;
-
- $lid->clear_recv_time;
- $e->update_acq_lineitem_detail($lid) or return 0;
-
- my $copy = $e->retrieve_asset_copy($lid->eg_copy_id) or return 0;
- $copy->status(OILS_COPY_STATUS_ON_ORDER);
- $copy->edit_date('now');
- $copy->editor($e->requestor->id);
- $e->update_asset_copy($copy) or return 0;
-
- $mgr->add_lid;
- return $lid;
-}
-
-# ----------------------------------------------------------------------------
-# Lineitem Attr
-# ----------------------------------------------------------------------------
-sub set_lineitem_attr {
- my($mgr, %args) = @_;
- my $attr_type = $args{attr_type};
-
- # first, see if it's already set. May just need to overwrite it
- my $attr = $mgr->editor->search_acq_lineitem_attr({
- lineitem => $args{lineitem},
- attr_type => $args{attr_type},
- attr_name => $args{attr_name}
- })->[0];
-
- if($attr) {
- $attr->attr_value($args{attr_value});
- return $attr if $mgr->editor->update_acq_lineitem_attr($attr);
- return undef;
-
- } else {
-
- $attr = Fieldmapper::acq::lineitem_attr->new;
- $attr->$_($args{$_}) for keys %args;
-
- unless($attr->definition) {
- my $find = "search_acq_$attr_type";
- my $attr_def_id = $mgr->editor->$find({code => $attr->attr_name}, {idlist=>1})->[0] or return 0;
- $attr->definition($attr_def_id);
- }
- return $mgr->editor->create_acq_lineitem_attr($attr);
- }
-}
-
-# ----------------------------------------------------------------------------
-# Lineitem Debits
-# ----------------------------------------------------------------------------
-sub create_lineitem_debits {
- my ($mgr, $li, $dry_run) = @_;
-
- unless($li->estimated_unit_price) {
- $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PRICE', payload => $li->id));
- $mgr->editor->rollback;
- return 0;
- }
-
- unless($li->provider) {
- $mgr->editor->event(OpenILS::Event->new('ACQ_LINEITEM_NO_PROVIDER', payload => $li->id));
- $mgr->editor->rollback;
- return 0;
- }
-
- my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
- {lineitem => $li->id},
- {idlist=>1}
- );
-
- for my $lid_id (@$lid_ids) {
-
- my $lid = $mgr->editor->retrieve_acq_lineitem_detail([
- $lid_id,
- { flesh => 1,
- flesh_fields => {acqlid => ['fund']}
- }
- ]);
-
- create_lineitem_detail_debit($mgr, $li, $lid, $dry_run) or return 0;
- }
-
- return 1;
-}
-
-
-# flesh li->provider
-# flesh lid->fund
-sub create_lineitem_detail_debit {
- my ($mgr, $li, $lid, $dry_run, $no_translate) = @_;
-
- # don't create the debit if one already exists
- return $mgr->editor->retrieve_acq_fund_debit($lid->fund_debit) if $lid->fund_debit;
-
- my $li_id = ref($li) ? $li->id : $li;
-
- unless(ref $li and ref $li->provider) {
- $li = $mgr->editor->retrieve_acq_lineitem([
- $li_id,
- { flesh => 1,
- flesh_fields => {jub => ['provider']},
- }
- ]);
- }
-
- if(ref $lid) {
- $lid->fund($mgr->editor->retrieve_acq_fund($lid->fund)) unless(ref $lid->fund);
- } else {
- $lid = $mgr->editor->retrieve_acq_lineitem_detail([
- $lid,
- { flesh => 1,
- flesh_fields => {acqlid => ['fund']}
- }
- ]);
- }
-
- unless ($lid->fund) {
- $mgr->editor->event(
- new OpenILS::Event("ACQ_FUND_NOT_FOUND") # close enough
- );
- return 0;
- }
-
- my $amount = $li->estimated_unit_price;
- if($li->provider->currency_type ne $lid->fund->currency_type and !$no_translate) {
-
- # At Fund debit creation time, translate into the currency of the fund
- # TODO: org setting to disable automatic currency conversion at debit create time?
-
- $amount = $mgr->editor->json_query({
- from => [
- 'acq.exchange_ratio',
- $li->provider->currency_type, # source currency
- $lid->fund->currency_type, # destination currency
- $li->estimated_unit_price # source amount
- ]
- })->[0]->{value};
- }
-
- my $debit = create_fund_debit(
- $mgr,
- $dry_run,
- fund => $lid->fund->id,
- origin_amount => $li->estimated_unit_price,
- origin_currency_type => $li->provider->currency_type,
- amount => $amount
- ) or return 0;
-
- $lid->fund_debit($debit->id);
- $lid->fund($lid->fund->id);
- $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
- return $debit;
-}
-
-
-__PACKAGE__->register_method(
- "method" => "fund_exceeds_balance_percent_api",
- "api_name" => "open-ils.acq.fund.check_balance_percentages",
- "signature" => {
- "desc" => q/Determine whether a given fund exceeds its defined
- "balance stop and warning percentages"/,
- "params" => [
- {"desc" => "Authentication token", "type" => "string"},
- {"desc" => "Fund ID", "type" => "number"},
- {"desc" => "Theoretical debit amount (optional)",
- "type" => "number"}
- ],
- "return" => {"desc" => q/An array of two values, for stop and warning,
- in that order: 1 if fund exceeds that balance percentage, else 0/}
- }
-);
-
-sub fund_exceeds_balance_percent_api {
- my ($self, $conn, $auth, $fund_id, $debit_amount) = @_;
-
- $debit_amount ||= 0;
-
- my $e = new_editor("authtoken" => $auth);
- return $e->die_event unless $e->checkauth;
-
- my $fund = $e->retrieve_acq_fund($fund_id) or return $e->die_event;
- return $e->die_event unless $e->allowed("VIEW_FUND", $fund->org);
-
- my $result = [
- fund_exceeds_balance_percent($fund, $debit_amount, $e, "stop"),
- fund_exceeds_balance_percent($fund, $debit_amount, $e, "warning")
- ];
-
- $e->disconnect;
- return $result;
-}
-
-sub fund_exceeds_balance_percent {
- my ($fund, $debit_amount, $e, $which) = @_;
-
- my ($method_name, $event_name) = @{{
- "warning" => [
- "balance_warning_percent", "ACQ_FUND_EXCEEDS_WARN_PERCENT"
- ],
- "stop" => [
- "balance_stop_percent", "ACQ_FUND_EXCEEDS_STOP_PERCENT"
- ]
- }->{$which}};
-
- if ($fund->$method_name) {
- my $balance =
- $e->search_acq_fund_combined_balance({"fund" => $fund->id})->[0];
- my $allocations =
- $e->search_acq_fund_allocation_total({"fund" => $fund->id})->[0];
-
- $balance = ($balance) ? $balance->amount : 0;
- $allocations = ($allocations) ? $allocations->amount : 0;
-
- if (
- $allocations == 0 || # if no allocations were ever made, assume we have hit the stop percent
- ((($allocations - $balance + $debit_amount) / $allocations) * 100) > $fund->$method_name
- ) {
- $logger->info("fund would hit a limit: " . $fund->id . ", $balance, $debit_amount, $allocations, $method_name");
- $e->event(
- new OpenILS::Event(
- $event_name,
- "payload" => {
- "fund" => $fund, "debit_amount" => $debit_amount
- }
- )
- );
- return 1;
- }
- }
- return 0;
-}
-
-# ----------------------------------------------------------------------------
-# Fund Debit
-# ----------------------------------------------------------------------------
-sub create_fund_debit {
- my($mgr, $dry_run, %args) = @_;
-
- # Verify the fund is not being spent beyond the hard stop amount
- my $fund = $mgr->editor->retrieve_acq_fund($args{fund}) or return 0;
-
- return 0 if
- fund_exceeds_balance_percent(
- $fund, $args{"amount"}, $mgr->editor, "stop"
- );
- return 0 if
- $dry_run and fund_exceeds_balance_percent(
- $fund, $args{"amount"}, $mgr->editor, "warning"
- );
-
- my $debit = Fieldmapper::acq::fund_debit->new;
- $debit->debit_type('purchase');
- $debit->encumbrance('t');
- $debit->$_($args{$_}) for keys %args;
- $debit->clear_id;
- $mgr->add_debit($debit->amount);
- return $mgr->editor->create_acq_fund_debit($debit);
-}
-
-
-# ----------------------------------------------------------------------------
-# Picklist
-# ----------------------------------------------------------------------------
-sub create_picklist {
- my($mgr, %args) = @_;
- my $picklist = Fieldmapper::acq::picklist->new;
- $picklist->creator($mgr->editor->requestor->id);
- $picklist->owner($picklist->creator);
- $picklist->editor($picklist->creator);
- $picklist->create_time('now');
- $picklist->edit_time('now');
- $picklist->org_unit($mgr->editor->requestor->ws_ou);
- $picklist->owner($mgr->editor->requestor->id);
- $picklist->$_($args{$_}) for keys %args;
- $picklist->clear_id;
- $mgr->picklist($picklist);
- return $mgr->editor->create_acq_picklist($picklist);
-}
-
-sub update_picklist {
- my($mgr, $picklist) = @_;
- $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
- $picklist->edit_time('now');
- $picklist->editor($mgr->editor->requestor->id);
- if ($mgr->editor->update_acq_picklist($picklist)) {
- $picklist = $mgr->editor->retrieve_acq_picklist($mgr->editor->data);
- $mgr->picklist($picklist);
- return $picklist;
- } else {
- return undef;
- }
-}
-
-sub delete_picklist {
- my($mgr, $picklist) = @_;
- $picklist = $mgr->editor->retrieve_acq_picklist($picklist) unless ref $picklist;
-
- # delete all 'new' lineitems
- my $li_ids = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => 'new'}, {idlist => 1});
- for my $li_id (@$li_ids) {
- my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
- return 0 unless delete_lineitem($mgr, $li);
- $mgr->respond;
- }
-
- # detach all non-'new' lineitems
- $li_ids = $mgr->editor->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}}, {idlist => 1});
- for my $li_id (@$li_ids) {
- my $li = $mgr->editor->retrieve_acq_lineitem($li_id);
- $li->clear_picklist;
- return 0 unless update_lineitem($mgr, $li);
- $mgr->respond;
- }
-
- # remove any picklist-specific object perms
- my $ops = $mgr->editor->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => ''.$picklist->id});
- for my $op (@$ops) {
- return 0 unless $mgr->editor->delete_usr_object_perm_map($op);
- }
-
- return $mgr->editor->delete_acq_picklist($picklist);
-}
-
-# ----------------------------------------------------------------------------
-# Purchase Order
-# ----------------------------------------------------------------------------
-sub update_purchase_order {
- my($mgr, $po) = @_;
- $po = $mgr->editor->retrieve_acq_purchase_order($po) unless ref $po;
- $po->editor($mgr->editor->requestor->id);
- $po->edit_time('now');
- $mgr->purchase_order($po);
- return $mgr->editor->retrieve_acq_purchase_order($mgr->editor->data)
- if $mgr->editor->update_acq_purchase_order($po);
- return undef;
-}
-
-sub create_purchase_order {
- my($mgr, %args) = @_;
-
- # verify the chosen provider is still active
- my $provider = $mgr->editor->retrieve_acq_provider($args{provider}) or return 0;
- unless($U->is_true($provider->active)) {
- $logger->error("provider is not active. cannot create PO");
- $mgr->editor->event(OpenILS::Event->new('ACQ_PROVIDER_INACTIVE'));
- return 0;
- }
-
- my $po = Fieldmapper::acq::purchase_order->new;
- $po->creator($mgr->editor->requestor->id);
- $po->editor($mgr->editor->requestor->id);
- $po->owner($mgr->editor->requestor->id);
- $po->edit_time('now');
- $po->create_time('now');
- $po->state('pending');
- $po->ordering_agency($mgr->editor->requestor->ws_ou);
- $po->$_($args{$_}) for keys %args;
- $po->clear_id;
- $mgr->purchase_order($po);
- return $mgr->editor->create_acq_purchase_order($po);
-}
-
-# ----------------------------------------------------------------------------
-# if all of the lineitems for this PO are received,
-# mark the PO as received
-# ----------------------------------------------------------------------------
-sub check_purchase_order_received {
- my($mgr, $po_id) = @_;
-
- my $non_recv_li = $mgr->editor->search_acq_lineitem(
- { purchase_order => $po_id,
- state => {'!=' => 'received'}
- }, {idlist=>1});
-
- my $po = $mgr->editor->retrieve_acq_purchase_order($po_id);
- return $po if @$non_recv_li;
-
- $po->state('received');
- return update_purchase_order($mgr, $po);
-}
-
-
-# ----------------------------------------------------------------------------
-# Bib, Callnumber, and Copy data
-# ----------------------------------------------------------------------------
-
-sub create_lineitem_assets {
- my($mgr, $li_id) = @_;
- my $evt;
-
- my $li = $mgr->editor->retrieve_acq_lineitem([
- $li_id,
- { flesh => 1,
- flesh_fields => {jub => ['purchase_order', 'attributes']}
- }
- ]) or return 0;
-
- # -----------------------------------------------------------------
- # first, create the bib record if necessary
- # -----------------------------------------------------------------
- my $new_bib = 0;
- unless($li->eg_bib_id) {
- create_bib($mgr, $li) or return 0;
- $new_bib = 1;
- }
-
-
- # -----------------------------------------------------------------
- # The lineitem is going live, promote user request holds to real holds
- # -----------------------------------------------------------------
- promote_lineitem_holds($mgr, $li) or return 0;
-
- my $li_details = $mgr->editor->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
-
- # -----------------------------------------------------------------
- # for each lineitem_detail, create the volume if necessary, create
- # a copy, and link them all together.
- # -----------------------------------------------------------------
- my $first_cn;
- for my $lid_id (@{$li_details}) {
-
- my $lid = $mgr->editor->retrieve_acq_lineitem_detail($lid_id) or return 0;
- next if $lid->eg_copy_id;
-
- # use the same callnumber label for all items within this lineitem
- $lid->cn_label($first_cn) if $first_cn and not $lid->cn_label;
-
- # apply defaults if necessary
- return 0 unless complete_lineitem_detail($mgr, $lid);
-
- $first_cn = $lid->cn_label unless $first_cn;
-
- my $org = $lid->owning_lib;
- my $label = $lid->cn_label;
- my $bibid = $li->eg_bib_id;
-
- my $volume = $mgr->cache($org, "cn.$bibid.$label");
- unless($volume) {
- $volume = create_volume($mgr, $li, $lid) or return 0;
- $mgr->cache($org, "cn.$bibid.$label", $volume);
- }
- create_copy($mgr, $volume, $lid, $li) or return 0;
- }
-
- return { li => $li, new_bib => $new_bib };
-}
-
-sub create_bib {
- my($mgr, $li) = @_;
-
- my $record = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
- $mgr->editor,
- $li->marc,
- undef, # bib source
- undef,
- 1, # override tcn collisions
- );
-
- if($U->event_code($record)) {
- $mgr->editor->event($record);
- $mgr->editor->rollback;
- return 0;
- }
-
- $li->eg_bib_id($record->id);
- $mgr->add_bib;
- return update_lineitem($mgr, $li);
-}
-
-sub create_volume {
- my($mgr, $li, $lid) = @_;
-
- my ($volume, $evt) =
- OpenILS::Application::Cat::AssetCommon->find_or_create_volume(
- $mgr->editor,
- $lid->cn_label,
- $li->eg_bib_id,
- $lid->owning_lib
- );
-
- if($evt) {
- $mgr->editor->event($evt);
- return 0;
- }
-
- return $volume;
-}
-
-sub create_copy {
- my($mgr, $volume, $lid, $li) = @_;
- my $copy = Fieldmapper::asset::copy->new;
- $copy->isnew(1);
- $copy->loan_duration(2);
- $copy->fine_level(2);
- $copy->status(($lid->recv_time) ? OILS_COPY_STATUS_IN_PROCESS : OILS_COPY_STATUS_ON_ORDER);
- $copy->barcode($lid->barcode);
- $copy->location($lid->location);
- $copy->call_number($volume->id);
- $copy->circ_lib($volume->owning_lib);
- $copy->circ_modifier($lid->circ_modifier);
-
- # AKA list price. We might need a $li->list_price field since
- # estimated price is not necessarily the same as list price
- $copy->price($li->estimated_unit_price);
-
- my $evt = OpenILS::Application::Cat::AssetCommon->create_copy($mgr->editor, $volume, $copy);
- if($evt) {
- $mgr->editor->event($evt);
- return 0;
- }
-
- $mgr->add_copy;
- $lid->eg_copy_id($copy->id);
- $mgr->editor->update_acq_lineitem_detail($lid) or return 0;
-}
-
-
-
-
-
-
-# ----------------------------------------------------------------------------
-# Workflow: Build a selection list from a Z39.50 search
-# ----------------------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'zsearch',
- api_name => 'open-ils.acq.picklist.search.z3950',
- stream => 1,
- signature => {
- desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'Search definition', type => 'object'},
- {desc => 'Picklist name, optional', type => 'string'},
- ]
- }
-);
-
-sub zsearch {
- my($self, $conn, $auth, $search, $name, $options) = @_;
- my $e = new_editor(authtoken=>$auth);
- return $e->event unless $e->checkauth;
- return $e->event unless $e->allowed('CREATE_PICKLIST');
-
- $search->{limit} ||= 10;
- $options ||= {};
-
- my $ses = OpenSRF::AppSession->create('open-ils.search');
- my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
-
- my $first = 1;
- my $picklist;
- my $mgr;
- while(my $resp = $req->recv(timeout=>60)) {
-
- if($first) {
- my $e = new_editor(requestor=>$e->requestor, xact=>1);
- $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
- $picklist = zsearch_build_pl($mgr, $name);
- $first = 0;
- }
-
- my $result = $resp->content;
- my $count = $result->{count} || 0;
- $mgr->total( (($count < $search->{limit}) ? $count : $search->{limit})+1 );
-
- for my $rec (@{$result->{records}}) {
-
- my $li = create_lineitem($mgr,
- picklist => $picklist->id,
- source_label => $result->{service},
- marc => $rec->{marcxml},
- eg_bib_id => $rec->{bibid}
- );
-
- if($$options{respond_li}) {
- $li->attributes($mgr->editor->search_acq_lineitem_attr({lineitem => $li->id}))
- if $$options{flesh_attrs};
- $li->clear_marc if $$options{clear_marc};
- $mgr->respond(lineitem => $li);
- } else {
- $mgr->respond;
- }
- }
- }
-
- $mgr->editor->commit;
- return $mgr->respond_complete;
-}
-
-sub zsearch_build_pl {
- my($mgr, $name) = @_;
- $name ||= '';
-
- my $picklist = $mgr->editor->search_acq_picklist({
- owner => $mgr->editor->requestor->id,
- name => $name
- })->[0];
-
- if($name eq '' and $picklist) {
- return 0 unless delete_picklist($mgr, $picklist);
- $picklist = undef;
- }
-
- return update_picklist($mgr, $picklist) if $picklist;
- return create_picklist($mgr, name => $name);
-}
-
-
-# ----------------------------------------------------------------------------
-# Workflow: Build a selection list / PO by importing a batch of MARC records
-# ----------------------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'upload_records',
- api_name => 'open-ils.acq.process_upload_records',
- stream => 1,
-);
-
-sub upload_records {
- my($self, $conn, $auth, $key) = @_;
-
- my $e = new_editor(authtoken => $auth, xact => 1);
- return $e->die_event unless $e->checkauth;
- my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
-
- my $cache = OpenSRF::Utils::Cache->new;
-
- my $data = $cache->get_cache("vandelay_import_spool_$key");
- my $purpose = $data->{purpose};
- my $filename = $data->{path};
- my $provider = $data->{provider};
- my $picklist = $data->{picklist};
- my $create_po = $data->{create_po};
- my $activate_po = $data->{activate_po};
- my $ordering_agency = $data->{ordering_agency};
- my $create_assets = $data->{create_assets};
- my $po;
- my $evt;
-
- unless(-r $filename) {
- $logger->error("unable to read MARC file $filename");
- $e->rollback;
- return OpenILS::Event->new('FILE_UPLOAD_ERROR', payload => {filename => $filename});
- }
-
- $provider = $e->retrieve_acq_provider($provider) or return $e->die_event;
-
- if($picklist) {
- $picklist = $e->retrieve_acq_picklist($picklist) or return $e->die_event;
- if($picklist->owner != $e->requestor->id) {
- return $e->die_event unless
- $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
- }
- $mgr->picklist($picklist);
- }
-
- if($create_po) {
-
- $po = create_purchase_order($mgr,
- ordering_agency => $ordering_agency,
- provider => $provider->id,
- state => 'on-order'
- ) or return $mgr->editor->die_event;
- }
-
- $logger->info("acq processing MARC file=$filename");
-
- my $batch = new MARC::Batch ('USMARC', $filename);
- $batch->strict_off;
-
- my $count = 0;
- my @li_list;
-
- while(1) {
-
- my ($err, $xml, $r);
- $count++;
-
- try {
- $r = $batch->next;
- } catch Error with {
- $err = shift;
- $logger->warn("Proccessing of record $count in set $key failed with error $err. Skipping this record");
- };
-
- next if $err;
- last unless $r;
-
- try {
- ($xml = $r->as_xml_record()) =~ s/\n//sog;
- $xml =~ s/^<\?xml.+\?\s*>//go;
- $xml =~ s/>\s+</></go;
- $xml =~ s/\p{Cc}//go;
- $xml = $U->entityize($xml);
- $xml =~ s/[\x00-\x1f]//go;
-
- } catch Error with {
- $err = shift;
- $logger->warn("Proccessing XML of record $count in set $key failed with error $err. Skipping this record");
- };
-
- next if $err or not $xml;
-
- my %args = (
- source_label => $provider->code,
- provider => $provider->id,
- marc => $xml,
- );
-
- $args{picklist} = $picklist->id if $picklist;
- if($po) {
- $args{purchase_order} = $po->id;
- $args{state} = 'pending-order';
- }
-
- my $li = create_lineitem($mgr, %args) or return $mgr->editor->die_event;
- $mgr->respond;
- $li->provider($provider); # flesh it, we'll need it later
-
- import_lineitem_details($mgr, $ordering_agency, $li) or return $mgr->editor->die_event;
- $mgr->respond;
-
- push(@li_list, $li->id);
- $mgr->respond;
- }
-
- my $die_event = activate_purchase_order_impl($mgr, $po->id) if $po and $activate_po;
- return $die_event if $die_event;
-
- $e->commit;
- unlink($filename);
- $cache->delete_cache('vandelay_import_spool_' . $key);
-
- if ($create_assets) {
- create_lineitem_list_assets($mgr, \@li_list) or return $e->die_event;
- }
-
- return $mgr->respond_complete;
-}
-
-sub import_lineitem_details {
- my($mgr, $ordering_agency, $li) = @_;
-
- my $holdings = $mgr->editor->json_query({from => ['acq.extract_provider_holding_data', $li->id]});
- return 1 unless @$holdings;
- my $org_path = $U->get_org_ancestors($ordering_agency);
- $org_path = [ reverse (@$org_path) ];
- my $price;
-
-
- my $idx = 1;
- while(1) {
- # create a lineitem detail for each copy in the data
-
- my $compiled = extract_lineitem_detail_data($mgr, $org_path, $holdings, $idx);
- last unless defined $compiled;
- return 0 unless $compiled;
-
- # this takes the price of the last copy and uses it as the lineitem price
- # need to determine if a given record would include different prices for the same item
- $price = $$compiled{estimated_price};
-
- last unless $$compiled{quantity};
-
- for(1..$$compiled{quantity}) {
- my $lid = create_lineitem_detail(
- $mgr,
- lineitem => $li->id,
- owning_lib => $$compiled{owning_lib},
- cn_label => $$compiled{call_number},
- fund => $$compiled{fund},
- circ_modifier => $$compiled{circ_modifier},
- note => $$compiled{note},
- location => $$compiled{copy_location},
- collection_code => $$compiled{collection_code}
- ) or return 0;
- }
-
- $mgr->respond;
- $idx++;
- }
-
- $li->estimated_unit_price($price);
- update_lineitem($mgr, $li) or return 0;
- return 1;
-}
-
-# return hash on success, 0 on error, undef on no more holdings
-sub extract_lineitem_detail_data {
- my($mgr, $org_path, $holdings, $index) = @_;
-
- my @data_list = grep { $_->{holding} eq $index } @$holdings;
- return undef unless @data_list;
-
- my %compiled = map { $_->{attr} => $_->{data} } @data_list;
- my $base_org = $$org_path[0];
-
- my $killme = sub {
- my $msg = shift;
- $logger->error("Item import extraction error: $msg");
- $logger->error('Holdings Data: ' . OpenSRF::Utils::JSON->perl2JSON(\%compiled));
- $mgr->editor->rollback;
- $mgr->editor->event(OpenILS::Event->new('ACQ_IMPORT_ERROR', payload => $msg));
- return 0;
- };
-
- # ---------------------------------------------------------------------
- # Fund
- if(my $code = $compiled{fund_code}) {
-
- my $fund = $mgr->cache($base_org, "fund.$code");
- unless($fund) {
- # search up the org tree for the most appropriate fund
- for my $org (@$org_path) {
- $fund = $mgr->editor->search_acq_fund(
- {org => $org, code => $code, year => DateTime->now->year}, {idlist => 1})->[0];
- last if $fund;
- }
- }
- return $killme->("no fund with code $code at orgs [@$org_path]") unless $fund;
- $compiled{fund} = $fund;
- $mgr->cache($base_org, "fund.$code", $fund);
- }
-
-
- # ---------------------------------------------------------------------
- # Owning lib
- if(my $sn = $compiled{owning_lib}) {
- my $org_id = $mgr->cache($base_org, "orgsn.$sn") ||
- $mgr->editor->search_actor_org_unit({shortname => $sn}, {idlist => 1})->[0];
- return $killme->("invalid owning_lib defined: $sn") unless $org_id;
- $compiled{owning_lib} = $org_id;
- $mgr->cache($$org_path[0], "orgsn.$sn", $org_id);
- }
-
-
- # ---------------------------------------------------------------------
- # Circ Modifier
- my $code = $compiled{circ_modifier};
-
- if(defined $code) {
-
- # verify this is a valid circ modifier
- return $killme->("invlalid circ_modifier $code") unless
- defined $mgr->cache($base_org, "mod.$code") or
- $mgr->editor->retrieve_config_circ_modifier($code);
-
- # if valid, cache for future tests
- $mgr->cache($base_org, "mod.$code", $code);
-
- } else {
- $compiled{circ_modifier} = get_default_circ_modifier($mgr, $base_org);
- }
-
-
- # ---------------------------------------------------------------------
- # Shelving Location
- if( my $name = $compiled{copy_location}) {
- my $loc = $mgr->cache($base_org, "copy_loc.$name");
- unless($loc) {
- for my $org (@$org_path) {
- $loc = $mgr->editor->search_asset_copy_location(
- {owning_lib => $org, name => $name}, {idlist => 1})->[0];
- last if $loc;
- }
- }
- return $killme->("Invalid copy location $name") unless $loc;
- $compiled{copy_location} = $loc;
- $mgr->cache($base_org, "copy_loc.$name", $loc);
- }
-
- return \%compiled;
-}
-
-
-
-# ----------------------------------------------------------------------------
-# Workflow: Given an existing purchase order, import/create the bibs,
-# callnumber and copy objects
-# ----------------------------------------------------------------------------
-
-__PACKAGE__->register_method(
- method => 'create_po_assets',
- api_name => 'open-ils.acq.purchase_order.assets.create',
- signature => {
- desc => q/Creates assets for each lineitem in the purchase order/,
- params => [
- {desc => 'Authentication token', type => 'string'},
- {desc => 'The purchase order id', type => 'number'},
- ],
- return => {desc => 'Streams a total versus completed counts object, event on error'}
- }
-);
-
-sub create_po_assets {
- my($self, $conn, $auth, $po_id) = @_;
-
- my $e = new_editor(authtoken=>$auth, xact=>1);
- return $e->die_event unless $e->checkauth;
- my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
-
- my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
-
- my $li_ids = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1});
-
- # it's ugly, but it's fast. Get the total count of lineitem detail objects to process
- my $lid_total = $e->json_query({
- select => { acqlid => [{aggregate => 1, transform => 'count', column => 'id'}] },
- from => {
- acqlid => {
- jub => {
- fkey => 'lineitem',
- field => 'id',
- join => {acqpo => {fkey => 'purchase_order', field => 'id'}}
- }
- }
- },
- where => {'+acqpo' => {id => $po_id}}
- })->[0]->{id};
-
- $mgr->total(scalar(@$li_ids) + $lid_total);
-
- create_lineitem_list_assets($mgr, $li_ids) or return $e->die_event;
-
- $e->xact_begin;
- update_purchase_order($mgr, $po) or return $e->die_event;
- $e->commit;
-
- return $mgr->respond_complete;
-}
-
-
-
-__PACKAGE__->register_method(
- method => 'create_purchase_order_api',