</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
- <create/>
- <retrieve/>
- <update/>
- <delete/>
+ <create permission="ADMIN_SERIAL_ITEM">
+ <context link="stream" jump="distribution" field="holding_lib" />
+ </create>
+ <retrieve permission="ADMIN_SERIAL_ITEM">
+ <context link="stream" jump="distribution" field="holding_lib" />
+ </retrieve>
+ <update permission="ADMIN_SERIAL_ITEM">
+ <context link="stream" jump="distribution" field="holding_lib" />
+ </update>
+ <delete permission="ADMIN_SERIAL_ITEM">
+ <context link="stream" jump="distribution" field="holding_lib" />
+ </delete>
</actions>
</permacrud>
</class>
# item methods
#
__PACKAGE__->register_method(
+ method => "create_item_safely",
+ api_name => "open-ils.serial.item.create",
+ api_level => 1,
+ stream => 1,
+ argc => 3,
+ signature => {
+ desc => q/Creates any number of items, respecting only a few of the
+ submitted fields, as the user shouldn't be able to freely set certain
+ ones/,
+ params => [
+ {name=> "authtoken", desc => "Authtoken for current user session",
+ type => "string"},
+ {name => "item", desc => "serial item",
+ type => "object", class => "sitem"},
+ {name => "count",
+ desc => "optional: how many items to make " .
+ "(default 1; 1-100 permitted)",
+ type => "number"}
+ ],
+ return => {
+ desc => "created items (a stream of them)",
+ type => "object", class => "sitem"
+ }
+ }
+);
+__PACKAGE__->register_method(
+ method => "update_item_safely",
+ api_name => "open-ils.serial.item.update",
+ api_level => 1,
+ stream => 1,
+ argc => 2,
+ signature => {
+ desc => q/Edit a serial item, respecting only a few of the
+ submitted fields, as the user shouldn't be able to freely set certain
+ ones/,
+ params => [
+ {name=> "authtoken", desc => "Authtoken for current user session",
+ type => "string"},
+ {name => "item", desc => "serial item",
+ type => "object", class => "sitem"},
+ ],
+ return => {
+ desc => "created item", type => "object", class => "sitem"
+ }
+ }
+);
+
+sub _set_safe_item_fields {
+ my $dest = shift;
+ my $source = shift;
+ my $requestor_id = shift;
+ # extra fields remain in @_
+
+ $dest->edit_date("now");
+ $dest->editor($requestor_id);
+
+ my @fields = qw/date_expected date_received status/;
+
+ for my $field (@fields, @_) {
+ $dest->$field($source->$field);
+ }
+}
+
+sub update_item_safely {
+ my ($self, $client, $auth, $item) = @_;
+
+ my $e = new_editor("xact" => 1, "authtoken" => $auth);
+ $e->checkauth or return $e->die_event;
+
+ my $orig = $e->retrieve_serial_item([
+ $item->id, {
+ "flesh" => 2, "flesh_fields" => {
+ "sitem" => ["stream"], "sstr" => ["distribution"]
+ }
+ }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ "ADMIN_SERIAL_ITEM", $orig->stream->distribution->holding_lib
+ );
+
+ _set_safe_item_fields($orig, $item, $e->requestor->id);
+ $e->update_serial_item($orig) or return $e->die_event;
+
+ $client->respond($e->retrieve_serial_item($item->id));
+ $e->commit or return $e->die_event;
+ undef;
+}
+
+sub create_item_safely {
+ my ($self, $client, $auth, $item, $count) = @_;
+
+ $count = int $count;
+ $count ||= 1;
+ return new OpenILS::Event(
+ "BAD_PARAMS", note => "Count should be from 1 to 100"
+ ) unless $count >= 1 and $count <= 100;
+
+ my $e = new_editor("xact" => 1, "authtoken" => $auth);
+ $e->checkauth or return $e->die_event;
+
+ my $stream = $e->retrieve_serial_stream([
+ $item->stream, {
+ "flesh" => 1, "flesh_fields" => {"sstr" => ["distribution"]}
+ }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ "ADMIN_SERIAL_ITEM", $stream->distribution->holding_lib
+ );
+
+ for (my $i = 0; $i < $count; $i++) {
+ my $actual = new Fieldmapper::serial::item;
+ $actual->creator($e->requestor->id);
+ _set_safe_item_fields(
+ $actual, $item, $e->requestor->id, "issuance", "stream"
+ );
+
+ $e->create_serial_item($actual) or return $e->die_event;
+ $client->respond($e->data);
+ }
+
+ $e->commit or return $e->die_event;
+ undef;
+}
+
+__PACKAGE__->register_method(
method => 'fleshed_item_alter',
api_name => 'open-ils.serial.item.fleshed.batch.update',
api_level => 1,
}
__PACKAGE__->register_method(
- "method" => "get_receivable_items",
+ "method" => "get_items_by",
"api_name" => "open-ils.serial.items.receivable.by_subscription",
"stream" => 1,
"signature" => {
],
"return" => {
"desc" => "All receivable items under a given subscription",
- "type" => "object"
+ "type" => "object", "class" => "sitem"
}
}
);
__PACKAGE__->register_method(
- "method" => "get_receivable_items",
+ "method" => "get_items_by",
"api_name" => "open-ils.serial.items.receivable.by_issuance",
"stream" => 1,
"signature" => {
],
"return" => {
"desc" => "All receivable items under a given issuance",
- "type" => "object"
+ "type" => "object", "class" => "sitem"
+ }
+ }
+);
+
+__PACKAGE__->register_method(
+ "method" => "get_items_by",
+ "api_name" => "open-ils.serial.items.by_issuance",
+ "stream" => 1,
+ "signature" => {
+ "desc" => "Return all items under a given issuance",
+ "params" => [
+ {"desc" => "Authtoken", "type" => "string"},
+ {"desc" => "Issuance ID", "type" => "number"},
+ ],
+ "return" => {
+ "desc" => "All items under a given issuance",
+ "type" => "object", "class" => "sitem"
}
}
);
-sub get_receivable_items {
- my ($self, $client, $auth, $term) = @_;
+sub get_items_by {
+ my ($self, $client, $auth, $term, $opts) = @_;
+
+ # Not to be used in the json_query, but after limiting by perm check.
+ $opts = {} unless ref $opts eq "HASH";
+ $opts->{"limit"} ||= 10000; # some existing users may want all results
+ $opts->{"offset"} ||= 0;
+ $opts->{"limit"} = int($opts->{"limit"});
+ $opts->{"offset"} = int($opts->{"offset"});
my $e = new_editor("authtoken" => $auth);
return $e->die_event unless $e->checkauth;
- # XXX permissions
-
my $by = ($self->api_name =~ /by_(\w+)$/)[0];
+ my $receivable = ($self->api_name =~ /receivable/);
my %where = (
"issuance" => {"issuance" => $term},
"subscription" => {"+siss" => {"subscription" => $term}}
);
- my $item_ids = $e->json_query(
+ my $item_rows = $e->json_query(
{
- "select" => {"sitem" => ["id"]},
- "from" => {"sitem" => "siss"},
+ "select" => {"sitem" => ["id"], "sdist" => ["holding_lib"]},
+ "from" => {
+ "sitem" => {
+ "siss" => {},
+ "sstr" => {"join" => {"sdist" => {}}}
+ }
+ },
"where" => {
- %{$where{$by}}, "date_received" => undef
+ %{$where{$by}}, $receivable ? ("date_received" => undef) : ()
},
"order_by" => {"sitem" => ["id"]}
}
) or return $e->die_event;
- return undef unless @$item_ids;
+ return undef unless @$item_rows;
+
+ my $skipped = 0;
+ my $returned = 0;
+ foreach (@$item_rows) {
+ last if $returned >= $opts->{"limit"};
+ next unless $e->allowed("RECEIVE_SERIAL", $_->{"holding_lib"});
+ if ($skipped < $opts->{"offset"}) {
+ $skipped++;
+ next;
+ }
- foreach (map { $_->{"id"} } @$item_ids) {
$client->respond(
$e->retrieve_serial_item([
- $_, {
+ $_->{"id"}, {
"flesh" => 3,
"flesh_fields" => {
- "sitem" => ["stream", "issuance"],
+ "sitem" => [qw/stream issuance unit creator editor/],
"sstr" => ["distribution"],
"sdist" => ["holding_lib"]
}
}
])
);
+ $returned++;
}
$e->disconnect;
);
};
+ openils.Util._userFullNameFields = [
+ "prefix", "first_given_name", "second_given_name",
+ "family_name", "suffix", "alias", "usrname"
+ ];
+
+ /**
+ * Return an array of all the name-related attributes, with nulls replaced
+ * by empty strings, from a given actor.usr fieldmapper object, to be used
+ * as the arguments to any string formatting function that wants them.
+ * Code to do this is duplicated all over the place and should be
+ * simplified as we go.
+ */
+ openils.Util.userFullName = function(user) {
+ return dojo.map(
+ openils.Util._userFullNameFields,
+ function(a) { return user[a]() || ""; }
+ );
+ };
+
+ /**
+ * Same as openils.Util.userFullName, but with a hash of results instead
+ * of an array (dojo.string.substitute(), for example, can use this too).
+ */
+ openils.Util.userFullNameHash = function(user) {
+ var hash = {};
+ dojo.forEach(
+ openils.Util._userFullNameFields,
+ function(a) { hash[a] = user[a]() || ""; }
+ );
+ return hash;
+ };
+
/**
* Wrapper for dojo.addOnLoad that verifies a valid login session is active
* before adding the function to the onload set
--- /dev/null
+dojo.require("dijit.form.Button");
+dojo.require("dijit.form.DateTextBox");
+dojo.require("dijit.form.TextBox");
+dojo.require("dijit.form.NumberSpinner");
+dojo.require("dijit.form.FilteringSelect");
+dojo.require("openils.widget.PCrudAutocompleteBox");
+dojo.require("openils.widget.AutoGrid");
+dojo.require("openils.widget.ProgressDialog");
+dojo.require("openils.PermaCrud");
+dojo.require("openils.CGI");
+
+var pcrud, cgi, issuance_id;
+var sitem_cache = {};
+
+function load_sitem_grid() {
+ sitem_grid.overrideEditWidgets.status = status_selector;
+ sitem_grid.overrideEditWidgets.status.shove = {}; /* sic */
+
+ sitem_grid.dataLoader = sitem_data_loader;
+ sitem_grid.dataLoader();
+}
+
+function load_siss_display() {
+ pcrud.retrieve(
+ "siss", issuance_id, {
+ "onresponse": function(r) {
+ if (r = openils.Util.readResponse(r)) {
+ var link = dojo.byId("siss_label_here");
+ link.onclick = function() {
+ location.href = oilsBasePath +
+ "/eg/serial/subscription?id=" +
+ r.subscription() + "&tab=issuances";
+ }
+ link.innerHTML = r.label();
+ prepare_create_dialog(r.subscription());
+ }
+ }
+ }
+ );
+}
+
+function sitem_data_loader() {
+ sitem_grid.resetStore();
+ sitem_grid.showLoadProgressIndicator();
+
+ fieldmapper.standardRequest(
+ ["open-ils.serial", "open-ils.serial.items.by_issuance"], {
+ "params": [
+ openils.User.authtoken, issuance_id, {
+ "limit": sitem_grid.displayLimit,
+ "offset": sitem_grid.displayOffset
+ }
+ ],
+ "async": true,
+ "onresponse": function(r) {
+ var item = openils.Util.readResponse(r);
+ sitem_cache[item.id()] = item;
+ sitem_grid.store.newItem(item.toStoreItem());
+ },
+ "oncomplete": function(r) {
+ sitem_grid.hideLoadProgressIndicator();
+ }
+ }
+ );
+}
+
+function _get_field(store, item, field) {
+ if (!item) return "";
+ var id = store.getValue(item, "id");
+ return sitem_cache[id][field]();
+}
+
+/* create the get_foo() functions used by our AutoGrid */
+["creator", "editor", "stream", "unit"].forEach(
+ function(field) {
+ window["get_" + field] = function(row_index, item) {
+ return _get_field(this.grid.store, item, field);
+ };
+ }
+);
+
+function format_user(user) {
+ return user ? user.usrname() : "";
+}
+
+function format_stream(stream) {
+ return stream ? (stream.routing_label() || "[None]") : ""; /* XXX i18n */
+}
+
+function format_unit(unit) {
+ return unit ? (unit.barcode() || "[None]") : ""; /* XXX i18n */
+}
+
+function update_sitem_safely(obj, opts, edit_pane) {
+ fieldmapper.standardRequest(
+ ["open-ils.serial", "open-ils.serial.item.update"], {
+ "params": [openils.User.authtoken, obj],
+ "async": true,
+ "oncomplete": function(r) {
+ if (r = openils.Util.readResponse(r)) {
+ if (edit_pane.onPostSubmit)
+ edit_pane.onPostSubmit(null, [r]);
+ }
+ }
+ }
+ );
+}
+
+function prepare_create_dialog(sub_id) {
+ pcrud.search(
+ "sdist", {"subscription": sub_id}, {
+ "id_list": true,
+ "async": true,
+ "oncomplete": function(r) {
+ if (r = openils.Util.readResponse(r)) {
+ new openils.widget.PCrudAutocompleteBox({
+ "fmclass": "sstr",
+ "searchAttr": "routing_label",
+ "hasDownArrow": true,
+ "name": "stream",
+ "store_options": {
+ "base_filter": {"distribution": r},
+ "honor_retrieve_all": true
+ }
+ }, "stream_selector");
+ }
+ }
+ }
+ );
+}
+
+function create_new_items(form) {
+ var item = new sitem();
+
+ item.issuance(issuance_id); /* from global */
+ item.stream(form.stream);
+ item.status(form.status);
+ item.date_expected(
+ form.date_expected ?
+ dojo.date.stamp.toISOString(form.date_expected) : null
+ );
+ item.date_received(
+ form.date_received ?
+ dojo.date.stamp.toISOString(form.date_received) : null
+ );
+
+ progress_dialog.show(true);
+ fieldmapper.standardRequest(
+ ["open-ils.serial", "open-ils.serial.item.create"], {
+ "params": [openils.User.authtoken, item, form.count],
+ "async": true,
+ "oncomplete": function(r) {
+ progress_dialog.hide();
+ if (r = openils.Util.readResponse(r)) {
+ sitem_grid.refresh();
+ }
+ }
+ }
+ );
+}
+
+openils.Util.addOnLoad(
+ function() {
+ cgi = new openils.CGI();
+ pcrud = new openils.PermaCrud();
+
+ issuance_id = cgi.param("issuance");
+ load_siss_display();
+ load_sitem_grid();
+ }
+);
return aou_id ? aou.findOrgUnit(aou_id).shortname() : "";
}
-function get_sdist(rowIndex, item) {
+function get_id_and_label(rowIndex, item) {
if (!item) return {"id": "", "label": ""};
return {
"id": this.grid.store.getValue(item, "id"),
};
}
+function format_siss_label(blob) {
+ if (!blob.id) return "";
+ return "<a href='" +
+ oilsBasePath + "/serial/list_item?issuance=" + blob.id +
+ "'>" + (blob.label ? blob.label : "[None]") + "</a>"; /* XXX i18n */
+}
+
function format_sdist_label(blob) {
if (!blob.id) return "";
var link = "<a href='" +
openils.Util.addOnLoad(
function() {
+ var tab_dispatch = {
+ "distributions": distributions_tab,
+ "issuances": issuances_tab
+ };
+
cgi = new openils.CGI();
pcrud = new openils.PermaCrud();
sub_id = cgi.param("id");
load_sub_grid(
sub_id,
- (cgi.param("tab") == "distributions") ?
- function() { tab_container.selectChild(distributions_tab); } :
- null
+ (cgi.param("tab") in tab_dispatch) ?
+ function() {
+ tab_container.selectChild(
+ tab_dispatch[cgi.param("tab")]
+ );
+ } : null
);
}
);
--- /dev/null
+[% WRAPPER default/base.tt2 %]
+[% ctx.page_title = "Items" %]
+[% BLOCK status_values %]
+ <option value="Expected">Expected</option>
+ <option value="Bindery">Bindery</option>
+ <option value="Bound">Bound</option>
+ <option value="Claimed">Claimed</option>
+ <option value="Discarded">Discarded</option>
+ <option value="Not Held">Not Held</option>
+ <option value="Not Published">Not Published</option>
+ <option value="Received">Received</option>
+[% END %]
+<style type="text/css">
+ .create-dialog-table td { padding: 0.35em 0; }
+ .create-dialog-table th {
+ padding-right: 1em;
+ text-align: right;
+ font-weight: bold;
+ }
+</style>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+ <div dojoType="dijit.layout.ContentPane"
+ layoutAlign="top" class="oils-header-panel">
+ <div>Items</div>
+ <div>
+ <button dojoType="dijit.form.Button"
+ onClick="create_dialog.show()">New Items</button>
+ <button dojoType="dijit.form.Button"
+ onClick="sitem_grid.refresh()">Refresh Grid</button>
+ <button dojoType="dijit.form.Button"
+ onClick="sitem_grid.delete_items()">
+ Delete Selected
+ </button>
+ </div>
+ </div>
+ <div>
+ Showing items attached to the issuance,
+ <em><a href="javascript:void(0);" id="siss_label_here"></a></em>.
+ </div>
+ <table jsId="sitem_grid"
+ dojoType="openils.widget.AutoGrid"
+ query="{id: '*'}"
+ fieldOrder="['id','creator','editor','create_date','edit_date',
+ 'stream','date_expected','date_received','status','unit']"
+ suppressFields="['issuance','uri','shadowed']"
+ suppressEditFields="['issuance','uri','shadowed','creator','editor','create_date','edit_date','unit','stream']"
+ showSequenceFields="true"
+ fmClass="sitem"
+ editPaneOnSubmit="update_sitem_safely"
+ showPaginator="true"
+ editOnEnter="true">
+ <thead>
+ <tr>
+ <th field="creator" get="get_creator" formatter="format_user">
+ </th>
+ <th field="editor" get="get_editor" formatter="format_user">
+ </th>
+ <th field="stream" get="get_stream" formatter="format_stream">
+ </th>
+ <th field="unit" get="get_unit" formatter="format_unit">
+ </th>
+ </tr>
+ </thead>
+ </table>
+ <div class="hidden">
+ <div jsId="create_dialog" dojoType="dijit.Dialog"
+ title="Create New Items" execute="create_new_items(arguments[0]);">
+ <table class="create-dialog-table">
+ <tr>
+ <th>How many items?</th>
+ <td>
+ <input dojoType="dijit.form.NumberSpinner" value="1"
+ name="count" constraints="{min: 1, max: 100}" />
+ </td>
+ </tr>
+ <tr>
+ <th>Stream</th>
+ <td>
+ <input id="stream_selector" />
+ </td>
+ </tr>
+ <tr>
+ <th>Date Expected</th>
+ <td>
+ <input dojoType="dijit.form.DateTextBox"
+ id="create-date-expected" name="date_expected"
+ required="false" />
+ </td>
+ </tr>
+ <tr>
+ <th>Date Received</th>
+ <td>
+ <input dojoType="dijit.form.DateTextBox"
+ name="date_received" required="false" />
+ </td>
+ </tr>
+ <tr>
+ <th>Status</th>
+ <td>
+ <select dojoType="dijit.form.FilteringSelect"
+ name="status">
+ [%- PROCESS status_values -%]
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" align="center">
+ <span dojoType="dijit.form.Button" type="submit">
+ Create
+ </span>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <select jsId="status_selector" dojoType="dijit.form.FilteringSelect">
+ [%- PROCESS status_values -%]
+ </select>
+ <div dojoType="openils.widget.ProgressDialog" jsId="progress_dialog">
+ </div>
+ </div>
+</div>
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/serial/list_item.js"></script>
+[% END %]
</div>
<!-- Issuances -->
- <div dojoType="dijit.layout.ContentPane"
+ <div dojoType="dijit.layout.ContentPane" jsId="issuances_tab"
title="Issuances" layoutAlign="client">
<script type="dojo/connect" event="onShow">
if (!iss_grid._fresh) {
<thead>
<tr>
<th field="label" width="35%"
- get="get_sdist" formatter="format_sdist_label"></th>
+ get="get_id_and_label" formatter="format_sdist_label"></th>
</tr>
</thead>
</table>
query="{id: '*'}"
editOnEnter="true"
showPaginator="true">
+ <thead>
+ <tr>
+ <th field="label" formatter="format_siss_label"
+ get="get_id_and_label"></th>
+ </tr>
+ </thead>
</table>
</div>
<div class="hidden">