<field reporter:label="Peer Record Maps" name="peer_record_maps" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Peer Records" name="peer_records" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Last Captured Hold" name="last_captured_hold" oils_persist:virtual="true" reporter:datatype="link"/>
+ <field reporter:label="Last Copy Inventory" name="last_copy_inventory" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Has Holds" name="holds_count" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Copy Tags" name="tags" oils_persist:virtual="true" reporter:datatype="link"/>
<field reporter:label="Copy Alerts" name="copy_alerts" oils_persist:virtual="true" reporter:datatype="link"/>
<link field="peer_record_maps" reltype="has_many" key="target_copy" map="" class="bpbcm"/>
<link field="peer_records" reltype="has_many" key="target_copy" map="peer_record" class="bpbcm"/>
<link field="last_captured_hold" reltype="has_a" key="current_copy" map="" class="alhr"/>
+ <link field="last_copy_inventory" reltype="might_have" key="copy" map="" class="alci"/>
<link field="floating" reltype="has_a" key="id" map="" class="cfg"/>
<link field="holds_count" reltype="might_have" key="id" map="" class="hasholdscount"/>
<link field="tags" reltype="has_many" key="copy" map="" class="acptcm"/>
</permacrud>
</class>
+ <class id="alci" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::last_copy_inventory" oils_persist:tablename="asset.last_copy_inventory" reporter:core="true" reporter:label="Last Copy Inventory">
+ <fields oils_persist:primary="id" oils_persist:sequence="asset.last_copy_inventory_id_seq">
+ <field reporter:label="Last Inventory ID" name="id" reporter:datatype="id"/>
+ <field reporter:label="Last Inventory Date" name="inventory_date" reporter:datatype="timestamp"/>
+ <field reporter:label="Last Inventory Workstation" name="inventory_workstation" reporter:datatype="link"/>
+ <field reporter:label="Copy" name="copy" reporter:datatype="link"/>
+ </fields>
+ <links>
+ <link field="inventory_workstation" reltype="has_a" key="id" map="" class="aws"/>
+ <link field="copy" reltype="has_a" key="id" map="" class="acp"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create/>
+ <retrieve/>
+ <update/>
+ <delete/>
+ </actions>
+ </permacrud>
+ </class>
+
<class id="ccat" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::copy_alert_type" oils_persist:tablename="config.copy_alert_type" reporter:label="Copy Alert Type" oils_persist:restrict_primary="100">
<fields oils_persist:primary="id" oils_persist:sequence="config.copy_alert_type_id_seq">
<field reporter:label="Id" name="id" reporter:selector="name" reporter:datatype="id"/>
return 1;
}
+__PACKAGE__->register_method(
+ method => "update_last_copy_inventory",
+ api_name => "open-ils.circ.circulation.update_last_copy_inventory");
+
+sub update_last_copy_inventory {
+ my( $self, $conn, $auth, $args ) = @_;
+ my $e = new_editor(authtoken=>$auth, xact=>1);
+ return $e->die_event unless $e->checkauth;
+
+ my $copies = $$args{copy_list};
+ foreach my $copyid (@$copies) {
+ my $copy = $e->retrieve_asset_copy($copyid);
+ my $alci = $e->search_asset_last_copy_inventory({copy => $copyid})->[0];
+
+ if($alci) {
+ $alci->inventory_date('now');
+ $alci->inventory_workstation($e->requestor->wsid);
+ $e->update_asset_last_copy_inventory($alci) or return $e->die_event;
+ } else {
+ my $alci = Fieldmapper::asset::last_copy_inventory->new;
+ $alci->inventory_date('now');
+ $alci->inventory_workstation($e->requestor->wsid);
+ $alci->copy($copy->id);
+ $e->create_asset_last_copy_inventory($alci) or return $e->die_event;
+ }
+
+ $copy->last_copy_inventory($alci);
+ }
+ $e->commit;
+ return 1;
+}
__PACKAGE__->register_method(
method => "set_circ_claims_returned",
remote_hold
backdate
reservation
+ do_inventory_update
+ last_copy_inventory
copy
copy_id
copy_barcode
$self->dont_change_lost_zero($dont_change_lost_zero);
}
+ my $last_copy_inventory = Fieldmapper::asset::last_copy_inventory->new;
+
+ if ($self->do_inventory_update) {
+ $last_copy_inventory->inventory_date('now');
+ $last_copy_inventory->inventory_workstation($self->editor->requestor->wsid);
+ $last_copy_inventory->copy($self->copy->id());
+ } else {
+ my $alci = $self->editor->search_asset_last_copy_inventory(
+ {copy => $self->copy->id}
+ );
+ $last_copy_inventory = $alci->[0]
+ }
+ $self->last_copy_inventory($last_copy_inventory);
+
if( $self->checkin_check_holds_shelf() ) {
$self->bail_on_events(OpenILS::Event->new('NO_CHANGE'));
$self->hold($U->fetch_open_hold_by_copy($self->copy->id));
);
}
+ if ($self->last_copy_inventory) {
+ # flesh some workstation fields before returning
+ $self->last_copy_inventory->inventory_workstation(
+ $self->editor->retrieve_actor_workstation([$self->last_copy_inventory->inventory_workstation])
+ );
+ }
+
+ if($self->last_copy_inventory && !$self->last_copy_inventory->id) {
+ my $alci = $self->editor->search_asset_last_copy_inventory(
+ {copy => $self->last_copy_inventory->copy}
+ );
+ if($alci->[0]) {
+ $self->last_copy_inventory->id($alci->[0]->id);
+ }
+ }
+ $self->copy->last_copy_inventory($self->last_copy_inventory);
+
for my $evt (@{$self->events}) {
my $payload = {};
$payload->{patron} = $self->patron;
$payload->{reservation} = $self->reservation
unless (not $self->reservation or $self->reservation->cancel_time);
+ $payload->{last_copy_inventory} = $self->last_copy_inventory;
+ if ($self->do_inventory_update) { $payload->{do_inventory_update} = 1; }
$evt->{payload} = $payload;
}
);
CREATE UNIQUE INDEX copy_part_map_cp_part_idx ON asset.copy_part_map (target_copy, part);
+CREATE TABLE asset.last_copy_inventory (
+ id SERIAL PRIMARY KEY,
+ inventory_workstation INTEGER REFERENCES actor.workstation (id) DEFERRABLE INITIALLY DEFERRED,
+ inventory_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ copy BIGINT NOT NULL
+);
+CREATE INDEX last_copy_inventory_copy_idx ON asset.last_copy_inventory (copy);
+
CREATE TABLE asset.opac_visible_copies (
id BIGSERIAL primary key,
copy_id BIGINT, -- copy id
END;
$f$ LANGUAGE PLPGSQL VOLATILE COST 50;
+CREATE OR REPLACE FUNCTION evergreen.asset_last_copy_inventory_copy_inh_fkey() RETURNS TRIGGER AS $f$
+BEGIN
+ PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
+ IF NOT FOUND THEN
+ RAISE foreign_key_violation USING MESSAGE = FORMAT(
+ $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
+ );
+ END IF;
+ RETURN NEW;
+END;
+$f$ LANGUAGE PLPGSQL VOLATILE COST 50;
+
CREATE CONSTRAINT TRIGGER inherit_asset_copy_alert_copy_fkey
AFTER UPDATE OR INSERT ON asset.copy_alert
DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_copy_alert_copy_inh_fkey();
AFTER UPDATE OR INSERT ON asset.copy_tag_copy_map
DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_copy_tag_copy_map_copy_inh_fkey();
+CREATE CONSTRAINT TRIGGER inherit_asset_last_copy_inventory_copy_fkey
+ AFTER UPDATE OR INSERT ON asset.last_copy_inventory
+ DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_last_copy_inventory_copy_inh_fkey();
+
ALTER TABLE asset.copy_note ADD CONSTRAINT asset_copy_note_creator_fkey FOREIGN KEY (creator) REFERENCES actor.usr (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE asset.call_number ADD CONSTRAINT asset_call_number_owning_lib_fkey FOREIGN KEY (owning_lib) REFERENCES actor.org_unit (id) DEFERRABLE INITIALLY DEFERRED;
--- /dev/null
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+CREATE TABLE asset.last_copy_inventory (
+ id SERIAL PRIMARY KEY,
+ inventory_workstation INTEGER REFERENCES actor.workstation (id) DEFERRABLE INITIALLY DEFERRED,
+ inventory_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
+ copy BIGINT NOT NULL
+);
+CREATE INDEX last_copy_inventory_copy_idx ON asset.last_copy_inventory (copy);
+
+CREATE OR REPLACE FUNCTION evergreen.asset_last_copy_inventory_copy_inh_fkey() RETURNS TRIGGER AS $f$
+BEGIN
+ PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
+ IF NOT FOUND THEN
+ RAISE foreign_key_violation USING MESSAGE = FORMAT(
+ $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
+ );
+ END IF;
+ RETURN NEW;
+END;
+$f$ LANGUAGE PLPGSQL VOLATILE COST 50;
+
+CREATE CONSTRAINT TRIGGER inherit_asset_last_copy_inventory_copy_fkey
+ AFTER UPDATE OR INSERT ON asset.last_copy_inventory
+ DEFERRABLE FOR EACH ROW EXECUTE PROCEDURE evergreen.asset_last_copy_inventory_copy_inh_fkey();
+
+COMMIT;
\ No newline at end of file
{{item['call_number.record.simple_record.title']}}
</a>
</eg-grid-field>
+ <eg-grid-field path="last_copy_inventory.inventory_date" datatype="timestamp" label="[% l('Inventory Date') %]"></eg-grid-field>
+ <eg-grid-field path="last_copy_inventory.inventory_workstation.name" label="[% l('Inventory Workstation') %]"></eg-grid-field>
</eg-grid>
{{item['call_number.record.simple_record.title']}}
</a>
</eg-grid-field>
+ <eg-grid-field path="last_copy_inventory.inventory_date" datatype="timestamp" label="[% l('Inventory Date') %]"></eg-grid-field>
+ <eg-grid-field path="last_copy_inventory.inventory_workstation.name" label="[% l('Inventory Workstation') %]"></eg-grid-field>
</eg-grid>
{{item['copy_alert_count']}}
<button ng-disabled="item['copy_alert_count'] <= 0" class="btn btn-sm btn-default" ng-click="col.handlers.copyAlertsEdit(item['id'])">[% l('Manage') %]</button>
</eg-grid-field>
+ <eg-grid-field label="[% l('Inventory Date') %]" datatype="timestamp" path="last_copy_inventory.inventory_date"></eg-grid-field>
+ <eg-grid-field label="[% l('Inventory Workstation') %]" path="last_copy_inventory.inventory_workstation.name"></eg-grid-field>
</eg-grid>
</div>
"[% l('One or more items could not be transferred. Override?') %]";
s.OVERRIDE_TRANSFER_COPIES_TO_MARKED_VOLUME_BODY =
"[% l('Reason(s) include: [_1]', '{{evt_desc}}') %]";
+ s.SUCCESS_UPDATE_INVENTORY =
+ "[% l('Updated most recent inventory data for selected items.') %]";
+ s.FAIL_UPDATE_INVENTORY =
+ "[% l('Failed to update recent inventory data for selected items.')%]"
}])
</script>
[% END %]
<li><a href ng-click="checkin()">[% l('Check In Items') %]</a></li>
<li><a href ng-click="renew()">[% l('Renew Items') %]</a></li>
<li><a href ng-click="cancel_transit()">[% l('Cancel Transit') %]</a></li>
+ <li><a href ng-click="update_inventory()">[% l('Update Inventory') %]</a></li>
<p><b>[% l('Mark') %]</b></p>
<li><a href ng-click="selectedHoldingsDamaged()">[% l('Item as Damaged') %]</a></li>
label="[% l('Renew Items') %]"></eg-grid-action>
<eg-grid-action handler="cancel_transit"
label="[% l('Cancel Transit') %]"></eg-grid-action>
+ <eg-grid-action handler="update_inventory"
+ label="[% l('Update Inventory') %]"></eg-grid-action>
<eg-grid-action handler="selectedHoldingsItemStatusTgrEvt" group="[% l('Show') %]"
label="[% l('Triggered Events') %]"></eg-grid-action>
{{item['copy_alert_count']}}
<button ng-disabled="item['copy_alert_count'] <= 0" class="btn btn-sm btn-default" ng-click="col.handlers.copyAlertsEdit(item['id'])">[% l('Manage') %]</button>
</eg-grid-field>
+ <eg-grid-field label="[% l('Inventory Date') %]" path="last_copy_inventory.inventory_date" datatype="timestamp"></eg-grid-field>
+ <eg-grid-field label="[% l('Inventory Workstation') %]" path="last_copy_inventory.inventory_workstation.name"></eg-grid-field>
</eg-grid>
</div>
<div class="flex-row">
+ <div class="flex-cell">[% l('Inventory Date') %]</div>
+ <div class="flex-cell well">{{copy.last_copy_inventory().inventory_date() | date:egDateAndTimeFormat}}</div>
+
+ <div class="flex-cell">[% l('Inventory Workstation') %]</div>
+ <div class="flex-cell well">{{copy.last_copy_inventory().inventory_workstation().name()}}</div>
+
+ <div class="flex-cell"></div>
+ <div class="flex-cell"></div>
+ <div class="flex-cell"></div>
+ <div class="flex-cell"></div>
+ </div>
+
+ <div class="flex-row">
<div class="flex-cell">[% l('Copy Alerts') %]</div>
<div class="flex-cell" id="item-status-alert-msg">
<button class="btn btn-default" ng-click="addCopyAlerts(copy.id())" >[% l('Add') %]</button>
<div ng-if="modifiers.manual_float" class="alert-danger pad-all-min">
[% l('Manual Floating Active') %]
</div>
+ <div ng-if="modifiers.do_inventory_update" class="alert-danger pad-all-min">
+ [% l('Update Inventory') %]
+ </div>
</div>
</div>
<!-- checkin form -->
<div class="row pad-vert">
- <div class="col-md-5">
+ <div class="col-md-4">
<form ng-submit="checkin(checkinArgs)" role="form" class="form-inline">
<div class="input-group">
</div>
</div>
- <div class="col-md-4" ng-if="!is_capture">
+ <div class="col-md-3" ng-if="!is_capture">
<div class="flex-row">
<div class="flex-cell"></div>
<div class="pad-horiz">[% l('Effective Date') %]</div>
<span>[% l('Manual Floating Active') %]</span>
</a>
</li>
+ <li>
+ <a href
+ ng-click="toggle_mod('do_inventory_update')">
+ <span ng-if="modifiers.do_inventory_update"
+ class="label label-success">✓</span>
+ <span ng-if="!modifiers.do_inventory_update"
+ class="label label-warning">✗</span>
+ <span>[% l('Update Inventory') %]</span>
+ </a>
+ </li>
</ul>
</div><!-- btn grp -->
</div><!-- col -->
<eg-grid-field path="au.*" parent-idl-class="au" hidden></eg-grid-field>
<eg-grid-field path="transit.*" parent-idl-class="atc" hidden></eg-grid-field>
<eg-grid-field path="hold.*" parent-idl-class="ahr" hidden></eg-grid-field>
+ <eg-grid-field path="acp.last_copy_inventory.inventory_date" label="[% l('Inventory Date') %]" datatype="timestamp" hidden></eg-grid-field>
+ <eg-grid-field path="acp.last_copy_inventory.inventory_workstation.name" label="[% l('Inventory Workstation')%]" hidden></eg-grid-field>
</eg-grid>
return deferred.promise;
}
+ // apply last inventory data to fetched bucket items
+ service.fetchRecentInventoryData = function(copy) {
+ return egCore.pcrud.search('alci',
+ {copy: copy.id},
+ {flesh: 2, flesh_fields: {alci: ['inventory_workstation']}}
+ ).then(function(alci) {
+ return alci;
+ });
+ }
+
return service;
}])
itemSvc.requestItems([$scope.args.copyId]);
}
+ $scope.update_inventory = function() {
+ itemSvc.updateInventory([$scope.args.copyId], null)
+ .then(function(res) {
+ $timeout(function() { location.href = location.href; }, 1000);
+ });
+ }
+
$scope.attach_to_peer_bib = function() {
itemSvc.attach_to_peer_bib([{
id : $scope.args.copyId,
.controller('ListCtrl',
['$scope','$q','$routeParams','$location','$timeout','$window','egCore',
'egGridDataProvider','egItem','egUser','$uibModal','egCirc','egConfirmDialog',
- 'egProgressDialog',
+ 'egProgressDialog', 'ngToast',
function($scope , $q , $routeParams , $location , $timeout , $window , egCore ,
egGridDataProvider , itemSvc , egUser , $uibModal , egCirc , egConfirmDialog,
- egProgressDialog) {
+ egProgressDialog, ngToast) {
var copyId = [];
var cp_list = $routeParams.idList;
itemSvc.add_copies_to_bucket(copy_list);
}
+ $scope.update_inventory = function() {
+ var copy_list = gatherSelectedHoldingsIds();
+ itemSvc.updateInventory(copy_list, $scope.gridControls.allItems()).then(function(res) {
+ if (res) {
+ $scope.gridControls.allItems(res);
+ ngToast.create(egCore.strings.SUCCESS_UPDATE_INVENTORY);
+ } else {
+ ngToast.warning(egCore.strings.FAIL_UPDATE_INVENTORY);
+ }
+ });
+ }
+
$scope.need_one_selected = function() {
var items = $scope.gridControls.selectedItems();
if (items.length == 1) return false;
// regardless of whether it matches the current item.
if (!barcode && itemSvc.copy && itemSvc.copy.id() == copyId) {
$scope.copy = itemSvc.copy;
+ if (itemSvc.last_copy_inventory && itemSvc.last_copy_inventory.copy() == copyId) {
+ $scope.last_copy_inventory = itemSvc.last_copy_inventory;
+ }
$scope.copy_alert_count = itemSvc.copy.copy_alerts().filter(function(aca) {
return !aca.ack_time();
}).length;
var copy = res.copy;
itemSvc.copy = copy;
+ if (res.last_copy_inventory) itemSvc.last_copy_inventory = res.last_copy_inventory;
$scope.copy = copy;
+ $scope.last_copy_inventory = res.last_copy_inventory;
$scope.copy_alert_count = copy.copy_alerts().filter(function(aca) {
return !aca.ack_time();
}).length;
};
service.prototype.flesh = {
- flesh : 2,
+ flesh : 3,
flesh_fields : {
- acp : ['status','location','circ_lib','parts','age_protect','copy_alerts'],
- acn : ['prefix','suffix','copies']
+ acp : ['status','location','circ_lib','parts','age_protect','copy_alerts', 'last_copy_inventory'],
+ acn : ['prefix','suffix','copies'],
+ alci : ['inventory_workstation']
}
}
} else {
modifiers.push('noop'); // AKA suppress holds and transits
modifiers.push('auto_print_holds_transits');
+ modifiers.push('do_inventory_update');
}
// set modifiers from stored preferences
params.retarget_mode = 'retarget';
}
}
+ if ($scope.modifiers.do_inventory_update) params.do_inventory_update = true;
egCore.hatch.setItem('circ.checkin.strict_barcode', $scope.strict_barcode);
+ egCore.hatch.setItem('circ.checkin.do_inventory_update', $scope.modifiers.do_inventory_update);
var options = {
check_barcode : $scope.strict_barcode,
no_precat_alert : $scope.modifiers.no_precat_alert,
auto_print_holds_transits :
$scope.modifiers.auto_print_holds_transits,
- suppress_popups : suppress_popups
+ suppress_popups : suppress_popups,
+ do_inventory_update : $scope.modifiers.do_inventory_update
};
return {params : params, options: options};
// track the item in the grid before sending the request
checkinSvc.checkins.unshift(row_item);
-
egCirc.checkin(params, options).then(
function(final_resp) {
row_item['copy_barcode'] = row_item.acp.barcode();
+ if (row_item.acp.last_copy_inventory().inventory_date() == "now")
+ row_item.acp.last_copy_inventory().inventory_date(Date.now());
+
if (row_item.mbts) {
var amt = Number(row_item.mbts.balance_owed());
if (amt != 0) {
data.record = payload.record;
data.acp = payload.copy;
data.acn = payload.volume ? payload.volume : payload.copy ? payload.copy.call_number() : null;
+ data.alci = egCore.idl.toHash(payload.last_copy_inventory, true);
data.au = payload.patron;
data.transit = payload.transit;
data.status = payload.status;
data.isbn = final_resp.evt[0].isbn;
data.route_to = final_resp.evt[0].route_to;
+
if (payload.circ) data.duration = payload.circ.duration();
if (payload.circ) data.circ_lib = payload.circ.circ_lib();
+ if (payload.do_inventory_update) {
+ if (payload.last_copy_inventory.id()) {
+ egCore.pcrud.update(payload.last_copy_inventory);
+ } else {
+ egCore.pcrud.create(payload.last_copy_inventory);
+ }
+ }
// for checkin, the mbts lives on the main circ
if (payload.circ && payload.circ.billable_transaction())
var final_resp = {evt : evt, params : params, options : options};
- var copy, hold, transit;
+ var copy, hold, transit, last_copy_inventory;
if (evt[0].payload) {
copy = evt[0].payload.copy;
hold = evt[0].payload.hold;
transit = evt[0].payload.transit;
+ last_copy_inventory = evt[0].payload.last_copy_inventory;
}
// track the barcode regardless of whether it's valid
};
service.flesh = {
- flesh : 3,
+ flesh : 4,
flesh_fields : {
acp : ['call_number','location','status','location','floating','circ_modifier',
- 'age_protect','circ_lib','copy_alerts', 'editor', 'circ_as_type'],
+ 'age_protect','circ_lib','copy_alerts', 'editor', 'circ_as_type', 'last_copy_inventory'],
acn : ['record','prefix','suffix','label_class'],
- bre : ['simple_record','creator','editor']
+ bre : ['simple_record','creator','editor'],
+ alci : ['inventory_workstation']
},
select : {
// avoid fleshing MARC on the bre
return fetchCopy(barcode, id).then(function(res) {
if(!res.copy) { return $q.when(); }
-
return fetchCirc(copyData.copy).then(function(res) {
if (copyData.circ) {
return fetchSummary(copyData.circ).then(function() {
}
+ service.updateInventory = function(copy_list, all_items, refresh) {
+ if (copy_list.length == 0) return;
+ return egCore.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.circulation.update_last_copy_inventory',
+ egCore.auth.token(), {copy_list: copy_list}
+ ).then(function(res) {
+ if (res) {
+ if (all_items) angular.forEach(copy_list, function(copy) {
+ angular.forEach(all_items, function(item) {
+ if (copy == item.id) {
+ egCore.pcrud.search('alci', {copy: copy},
+ {flesh: 1, flesh_fields:
+ {alci: ['inventory_workstation']}
+ }).then(function(alci) {
+ item._last_copy_inventory.inventory_date = alci.inventory_date();
+ item._last_copy_inventory._inventory_workstation_name =
+ alci.inventory_workstation().name();
+ });
+ }
+ });
+ });
+ return all_items || res;
+ }
+ });
+ }
+
service.add_copies_to_bucket = function(copy_list) {
if (copy_list.length == 0) return;