<field name="record" reporter:datatype="link" />
<field name="atag" reporter:datatype="link" />
<field name="value" reporter:datatype="text" />
+ <field name="thesaurus" reporter:datatype="text" />
</fields>
<links>
<link field="record" reltype="has_a" key="id" map="" class="are"/>
import {AuthorityRoutingModule} from './routing.module';
import {MarcEditModule} from '@eg/staff/share/marc-edit/marc-edit.module';
import {AuthorityMarcEditComponent} from './marc-edit.component';
+import {BrowseAuthorityComponent} from './browse.component';
+import {ManageAuthorityComponent} from './manage.component';
+import {AuthorityMergeDialogComponent} from './merge-dialog.component';
+import {BrowseService} from './browse.service';
+import {BibListModule} from '@eg/staff/share/bib-list/bib-list.module';
@NgModule({
declarations: [
- AuthorityMarcEditComponent
+ AuthorityMarcEditComponent,
+ BrowseAuthorityComponent,
+ ManageAuthorityComponent,
+ AuthorityMergeDialogComponent
],
imports: [
StaffCommonModule,
CommonWidgetsModule,
MarcEditModule,
- AuthorityRoutingModule
+ AuthorityRoutingModule,
+ BibListModule
],
providers: [
+ BrowseService
]
})
--- /dev/null
+<eg-staff-banner bannerText="Manage Authority Records" i18n-bannerText>
+</eg-staff-banner>
+
+<eg-string #rowSelected text="Row Selected for Merge" i18n-text></eg-string>
+
+<eg-authority-merge-dialog #mergeDialog></eg-authority-merge-dialog>
+
+<div class="row form-inline mb-3">
+ <div class="col-lg-3">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" id="search-term" i18n>Search Term</span>
+ </div>
+ <input type="text" class="form-control" placeholder="Search Term"
+ i18n-placeholder aria-describedby="search-term"
+ (change)="search(null, true)"
+ (keyup.enter)="search(null, true)" [(ngModel)]="browse.searchTerm">
+ </div>
+ </div>
+ <div class="col-lg-5">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" id="auth-axis" i18n>Authority Type</span>
+ </div>
+ <eg-combobox #axisCbox [(ngModel)]="authorityAxis"
+ [entries]="browse.authorityAxes" (onChange)="search(null, true)">
+ </eg-combobox>
+ <!--
+ Hiding 'submit' button since it should never be necessary, plus it
+ can lead to firing duplicate searches if you're quick on the draw.
+ If we want it back, uncomment and add a [disabled] attribute to
+ prevent dupe searches.
+ <button class="btn btn-outline-dark ml-2" (click)="search()" i18n>Submit</button>
+ -->
+ </div>
+ </div>
+ <div class="col-lg-4 d-flex">
+ <div class="flex-1"></div><!-- push right -->
+ <div class="form-inline">
+ <button class="btn btn-outline-dark ml-2" (click)="search(-1)" i18n>Previous</button>
+ <label for='offset-input' class="form-control ml-2" i18n>Page</label>
+ <input class="form-control" type="number"
+ [(ngModel)]="browse.searchOffset" id="offset-input" (change)="search()"/>
+ <button class="btn btn-outline-dark ml-2" (click)="search(1)" i18n>Next</button>
+ </div>
+ </div>
+</div>
+
+<ng-template #headingTemplate let-row="row">
+ <a routerLink="/staff/cat/authority/manage/{{row.authority.id()}}/bibs"
+ i18n-title title="Manage Authority {{row.authority.id()}}">
+ {{row.heading}}
+ </a>
+</ng-template>
+
+<eg-grid #grid [dataSource]="dataSource" [disablePaging]="true"
+ [rowFlairIsEnabled]="true" [rowFlairCallback]="rowFlairCallback"
+ [cellTextGenerator]="cellTextGenerator" persistKey="cat.authority.browse">
+
+ <eg-grid-toolbar-action label="Mark for Merge" i18n-label
+ (onClick)="markForMerge($event)"></eg-grid-toolbar-action>
+
+ <eg-grid-toolbar-action label="Un-Mark for Merge" i18n-label
+ (onClick)="unMarkForMerge($event)"></eg-grid-toolbar-action>
+
+ <eg-grid-toolbar-action label="Clear All Merge Marks" i18n-label
+ (onClick)="clearMergeSelection()"></eg-grid-toolbar-action>
+
+ <eg-grid-toolbar-action label="Merge Marked Records" i18n-label
+ (onClick)="openMergeDialog()"></eg-grid-toolbar-action>
+
+ <eg-grid-column name="id" label="ID" path="authority.id" i18n-label
+ [index]="true" flex="1"></eg-grid-column>
+ <eg-grid-column name="link_count" label="Linked Bibs"
+ i18n-label flex="1"></eg-grid-column>
+ <eg-grid-column name="heading" label="Heading" i18n-label flex="3"
+ [cellTemplate]="headingTemplate"></eg-grid-column>
+ <eg-grid-column name="control_set" path="authority.control_set.name"
+ label="Control Set" i18n-label flex="1"></eg-grid-column>
+ <eg-grid-column name="thesaurus" label="Thesaurus" i18n-label flex="1"></eg-grid-column>
+ <eg-grid-column name="thesaurus_code" label="Thesaurus Code"
+ i18n-label flex="1"></eg-grid-column>
+ <eg-grid-column name="creator" label="Creator" i18n-label
+ path="authority.creator.usrname" flex="1"></eg-grid-column>
+ <eg-grid-column name="create_date" label="Create Date" i18n-label
+ path="authority.create_date" flex="1" datatype="timestamp"></eg-grid-column>
+ <eg-grid-column name="edit_date" label="Edit Date" i18n-label
+ path="authority.edit_date" flex="1" datatype="timestamp"></eg-grid-column>
+ <eg-grid-column name="source" label="Source" i18n-label
+ path="authority.source" flex="1"></eg-grid-column>
+ <eg-grid-column name="owner" label="Owner" i18n-label
+ path="authority.owner" flex="1"></eg-grid-column>
+</eg-grid>
+
--- /dev/null
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {Observable, empty} from 'rxjs';
+import {map, switchMap} from 'rxjs/operators';
+import {IdlObject} from '@eg/core/idl.service';
+import {Pager} from '@eg/share/util/pager';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {GridContext, GridDataSource, GridCellTextGenerator,
+ GridRowFlairEntry} from '@eg/share/grid/grid';
+import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {BrowseService} from './browse.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {AuthorityMergeDialogComponent} from './merge-dialog.component';
+
+/* Find, merge, and edit authority records */
+
+@Component({
+ templateUrl: 'browse.component.html',
+ styles: [`#offset-input { width: 4em; }`]
+})
+export class BrowseAuthorityComponent implements OnInit {
+
+ authorityAxis: ComboboxEntry;
+ dataSource: GridDataSource;
+ cellTextGenerator: GridCellTextGenerator;
+
+ rowFlairCallback: (row: any) => GridRowFlairEntry;
+
+ @ViewChild('grid', {static: false}) grid: GridComponent;
+ @ViewChild('axisCbox', {static: false}) axisCbox: ComboboxComponent;
+ @ViewChild('rowSelected', {static: false}) rowSelected: StringComponent;
+ @ViewChild('mergeDialog', {static: false})
+ mergeDialog: AuthorityMergeDialogComponent;
+
+ constructor(
+ private net: NetService,
+ private org: OrgService,
+ private pcrud: PcrudService,
+ public browse: BrowseService
+ ) {}
+
+ ngOnInit() {
+ this.browse.fetchAxes();
+ this.setupGrid();
+ }
+
+ setupGrid() {
+ this.dataSource = new GridDataSource();
+
+ this.dataSource.getRows =
+ (pager: Pager, sort: any): Observable<any> => {
+
+ if (this.authorityAxis) {
+ this.browse.authorityAxis = this.authorityAxis.id;
+
+ } else {
+ // Our browse service may have cached search params
+ if (this.browse.authorityAxis) {
+ this.axisCbox.selectedId = this.browse.authorityAxis;
+ this.authorityAxis = this.axisCbox.selected;
+ } else {
+ return empty();
+ }
+ }
+
+ return this.browse.loadAuthorities();
+ };
+
+ this.cellTextGenerator = {
+ heading: row => row.heading
+ };
+
+ this.rowFlairCallback = (row: any): GridRowFlairEntry => {
+ const flair = {icon: null, title: null};
+ if (this.browse.markedForMerge[row.authority.id()]) {
+ flair.icon = 'merge_type';
+ flair.title = this.rowSelected.text;
+ }
+ return flair;
+ };
+ }
+
+
+ markForMerge(rows: any[]) {
+ rows.forEach(row =>
+ this.browse.markedForMerge[row.authority.id()] = row);
+ }
+
+ unMarkForMerge(rows: any[]) {
+ rows.forEach(row =>
+ delete this.browse.markedForMerge[row.authority.id()]);
+ }
+
+ clearMergeSelection() {
+ this.browse.markedForMerge = {};
+ }
+
+ search(offset?: number, isNew?: boolean) {
+ if (offset) {
+ this.browse.searchOffset += offset;
+ } else if (isNew) {
+ this.browse.searchOffset = 0;
+ }
+ this.grid.reload();
+ }
+
+ openMergeDialog() {
+ const rows = Object.values(this.browse.markedForMerge);
+ if (rows.length > 0) {
+ this.mergeDialog.authData = rows;
+ this.mergeDialog.open({size: 'lg'}).subscribe(success => {
+ if (success) {
+ this.clearMergeSelection();
+ this.search();
+ }
+ });
+ }
+ }
+}
+
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable, empty} from 'rxjs';
+import {map, switchMap} from 'rxjs/operators';
+import {IdlObject} from '@eg/core/idl.service';
+import {Pager} from '@eg/share/util/pager';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+/* Browse APIS and state maintenance */
+
+@Injectable()
+export class BrowseService {
+
+ // Grid paging is disabled in this UI to support browsing in
+ // both directions. Define our own paging trackers.
+ pageSize = 15;
+ searchOffset = 0;
+
+ searchTerm: string;
+ authorityAxis: string;
+ authorityAxes: ComboboxEntry[];
+ markedForMerge: {[id: number]: boolean} = {};
+
+ constructor(
+ private net: NetService,
+ private org: OrgService,
+ private pcrud: PcrudService
+ ) {}
+
+ fetchAxes(): Promise<any> {
+ if (this.authorityAxes) {
+ return Promise.resolve(this.authorityAxes);
+ }
+
+ this.pcrud.retrieveAll('aba', {}, {atomic: true})
+ .pipe(map(axes => {
+ this.authorityAxes = axes
+ .map(axis => ({id: axis.code(), label: axis.name()}))
+ .sort((a1, a2) => a1.label < a2.label ? -1 : 1);
+ })).toPromise();
+
+ }
+
+ loadAuthorities(): Observable<any> {
+
+ if (!this.searchTerm || !this.authorityAxis) {
+ return empty();
+ }
+
+ return this.net.request(
+ 'open-ils.supercat',
+ 'open-ils.supercat.authority.browse.by_axis',
+ this.authorityAxis, this.searchTerm,
+ this.pageSize, this.searchOffset
+
+ ).pipe(switchMap(authIds => {
+
+ return this.net.request(
+ 'open-ils.search',
+ 'open-ils.search.authority.main_entry', authIds
+ );
+
+ })).pipe(map(authMeta => {
+
+ const oOrg = this.org.get(authMeta.authority.owner());
+
+ return {
+ authority: authMeta.authority,
+ link_count: authMeta.linked_bibs.length,
+ heading: authMeta.heading,
+ thesaurus: authMeta.thesaurus,
+ thesaurus_code: authMeta.thesaurus_code,
+ owner: oOrg ? oOrg.shortname() : ''
+ };
+ }));
+ }
+}
+
+
--- /dev/null
+<eg-staff-banner bannerText="#{{authId}} {{authMeta ? authMeta.heading : ''}}" i18n-bannerText>
+</eg-staff-banner>
+
+<div class="row mb-2">
+ <div class="col-lg-3">
+ <a routerLink="/staff/cat/authority/browse">
+ <button class="btn btn-outline-dark">
+ <span class="material-icons material-mat-icon-shrunk-in-button">arrow_back</span>
+ <span class="pl-1" i18n>Return to Browse</span>
+ </button>
+ </a>
+ </div>
+</div>
+
+<ngb-tabset #authTabs [activeId]="authTab"
+ (tabChange)="beforeTabChange($event)">
+ <ngb-tab title="Linked Bibs" i18n-title id="bibs">
+ <ng-template ngbTabContent>
+ <div class="mt-3" *ngIf="authMeta">
+ <eg-bib-list #bibList [bibIds]="authMeta.linked_bibs"
+ gridPersistKey="cat.authority.manage.bibs"></eg-bib-list>
+ </div>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Edit" i18n-title id="edit">
+ <ng-template ngbTabContent>
+ <div class="mt-3">
+ <eg-marc-editor #marcEditor recordType="authority" [recordId]="authId">
+ </eg-marc-editor>
+ </div>
+ </ng-template>
+ </ngb-tab>
+</ngb-tabset>
+
+
--- /dev/null
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {Observable, empty} from 'rxjs';
+import {map, switchMap} from 'rxjs/operators';
+import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {IdlObject} from '@eg/core/idl.service';
+import {Pager} from '@eg/share/util/pager';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+/* Find, merge, and edit authority records */
+
+@Component({
+ templateUrl: 'manage.component.html'
+})
+export class ManageAuthorityComponent implements OnInit {
+
+ authId: number;
+ authTab = 'bibs';
+ authMeta: any;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private net: NetService,
+ private org: OrgService,
+ private pcrud: PcrudService
+ ) {
+ }
+
+ ngOnInit() {
+ this.route.paramMap.subscribe((params: ParamMap) => {
+ this.authTab = params.get('tab') || 'bibs';
+ const id = +params.get('id');
+
+ if (id !== this.authId) {
+ this.authId = id;
+
+ this.net.request(
+ 'open-ils.search',
+ 'open-ils.search.authority.main_entry', this.authId
+ ).subscribe(meta => this.authMeta = meta);
+ }
+ });
+ }
+
+ // Changing a tab in the UI means changing the route.
+ // Changing the route ultimately results in changing the tab.
+ beforeTabChange(evt: NgbTabChangeEvent) {
+
+ // prevent tab changing until after route navigation
+ evt.preventDefault();
+
+ this.authTab = evt.nextId;
+ this.routeToTab();
+ }
+
+ routeToTab() {
+ const url =
+ `/staff/cat/authority/manage/${this.authId}/${this.authTab}`;
+ this.router.navigate([url]);
+ }
+}
+
+
--- /dev/null
+
+<eg-string #successMsg
+ text="Successfully Merged Authority Records" i18n-text></eg-string>
+<eg-string #errorMsg
+ text="Failed To Merge Authority Records" i18n-text></eg-string>
+
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title">
+ <span i18n>Merge Authority Records</span>
+ </h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <div class="row d-flex justify-content-center">
+ <h5>Merge {{authData.length}} Records?</h5>
+ </div>
+ <div class="row">
+ <div class="col-lg-2" i18n>Lead Record</div>
+ </div>
+ <div class="row" *ngFor="let data of authData">
+ <div class="col-lg-2">
+ <input type="radio" name="leadRecord"
+ [value]="data.authority.id()" [(ngModel)]="leadRecord"/>
+ </div>
+ <div class="col-lg-1">#{{data.authority.id()}}</div>
+ <div class="col-lg-6">{{data.heading}}</div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <ng-container *ngIf="!chargeResponse">
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Cancel</button>
+ <button type="button" class="btn btn-success"
+ (click)="merge()" i18n>Merge Records</button>
+ </ng-container>
+ </div>
+</ng-template>
+
--- /dev/null
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {AuthService} from '@eg/core/auth.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+
+/**
+ * Dialog for merging authority records.
+ */
+
+@Component({
+ selector: 'eg-authority-merge-dialog',
+ templateUrl: 'merge-dialog.component.html'
+})
+
+export class AuthorityMergeDialogComponent
+ extends DialogComponent implements OnInit {
+
+ // Rows passed from the authority browse grid.
+ @Input() authData: any[] = [];
+
+ leadRecord: number;
+
+ @ViewChild('successMsg', {static: true})
+ private successMsg: StringComponent;
+
+ @ViewChild('errorMsg', {static: true})
+ private errorMsg: StringComponent;
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private toast: ToastService,
+ private net: NetService,
+ private evt: EventService,
+ private auth: AuthService) {
+ super(modal); // required for subclassing
+ }
+
+ ngOnInit() {
+ this.onOpen$.subscribe(_ => {
+ if (this.authData.length > 0) {
+ this.leadRecord = this.authData[0].authority.id();
+ }
+ });
+ }
+
+ merge() {
+
+ const list = this.authData
+ .map(data => data.authority.id())
+ .filter(id => id !== this.leadRecord);
+
+ this.net.request('open-ils.cat',
+ 'open-ils.cat.authority.records.merge',
+ this.auth.token(), this.leadRecord, list)
+ .subscribe(resp => {
+ const evt = this.evt.parse(resp);
+
+ if (evt) {
+ this.errorMsg.current().then(str => this.toast.warning(str));
+ this.close(false);
+ } else {
+ this.successMsg.current().then(str => this.toast.success(str));
+ this.close(true);
+ }
+ });
+ }
+}
+
+
+
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {AuthorityMarcEditComponent} from './marc-edit.component';
+import {BrowseAuthorityComponent} from './browse.component';
+import {ManageAuthorityComponent} from './manage.component';
const routes: Routes = [{
path: 'edit',
}, {
path: 'edit/:id',
component: AuthorityMarcEditComponent
+ }, {
+ path: 'browse',
+ component: BrowseAuthorityComponent
+ }, {
+ path: 'manage/:id/:tab',
+ component: ManageAuthorityComponent
+ }, {
+ path: 'manage/:id/:tab',
+ component: ManageAuthorityComponent
}];
@NgModule({
<span i18n>Link Checker</span>
</a>
<div class="dropdown-divider"></div>
- <a href="/eg/staff/cat/catalog/manageAuthorities" class="dropdown-item">
+ <a routerLink="/staff/cat/authority/browse" class="dropdown-item">
<span class="material-icons" aria-hidden="true">lock</span>
<span i18n>Manage Authorities</span>
</a>
--- /dev/null
+<ng-template #titleTemplate let-row="row">
+ <a routerLink="/staff/catalog/record/{{row.id()}}"
+ i18n-title title="View Record {{row.id()}}">
+ {{row.title()}}
+ </a>
+</ng-template>
+
+<eg-grid #grid [dataSource]="dataSource" idlClass="rmsr" [sortable]="true"
+ [cellTextGenerator]="cellTextGenerator" [persistKey]="gridPersistKey"
+ [showDeclaredFieldsOnly]="true">
+
+ <eg-grid-column name="id" label="ID" i18n-label flex="1"></eg-grid-column>
+
+ <eg-grid-column name="title" [cellTemplate]="titleTemplate"
+ label="Title" i18n-label flex="3"></eg-grid-column>
+
+ <eg-grid-column name="author" label="Author" i18n-label></eg-grid-column>
+
+ <eg-grid-column name="creator" label="Creator" i18n-label [sortable]="false"
+ path="biblio_record.creator.usrname" flex="1"></eg-grid-column>
+
+ <eg-grid-column name="create_date" label="Create Date" i18n-label
+ [sortable]="false" path="biblio_record.create_date" flex="1"></eg-grid-column>
+
+ <eg-grid-column name="editor" label="Editor" i18n-label [sortable]="false"
+ path="biblio_record.editor.usrname" flex="1"></eg-grid-column>
+
+ <eg-grid-column name="edit_date" label="Edit Date" i18n-label
+ [sortable]="false" path="biblio_record.edit_date" flex="1"></eg-grid-column>
+
+</eg-grid>
--- /dev/null
+import {Component, Input, OnInit, ViewChild} from '@angular/core';
+import {Observable, empty} from 'rxjs';
+import {map, switchMap} from 'rxjs/operators';
+import {IdlObject} from '@eg/core/idl.service';
+import {Pager} from '@eg/share/util/pager';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {GridContext, GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+
+/* Grid of bib records and associated actions. */
+
+@Component({
+ templateUrl: 'bib-list.component.html',
+ selector: 'eg-bib-list'
+})
+export class BibListComponent implements OnInit {
+
+ // Display bibs linked to this authority record.
+ @Input() bibIds: number[];
+ @Input() gridPersistKey: string;
+
+ dataSource: GridDataSource;
+ cellTextGenerator: GridCellTextGenerator;
+
+ @ViewChild('grid', {static: false}) grid: GridComponent;
+
+ constructor(
+ private net: NetService,
+ private org: OrgService,
+ private pcrud: PcrudService
+ ) {
+ }
+
+ ngOnInit() {
+ this.dataSource = new GridDataSource();
+
+ this.dataSource.getRows = (pager: Pager, sort: any): Observable<any> => {
+
+ if (this.bibIds) {
+ return this.loadIds(pager, sort);
+ }
+
+ return empty();
+ };
+
+ this.cellTextGenerator = {
+ title: row => row.title
+ };
+ }
+
+ loadIds(pager: Pager, sort: any): Observable<any> {
+ if (this.bibIds.length === 0) {
+ return empty();
+ }
+
+ const orderBy: any = {rmsr: 'title'};
+ if (sort.length) {
+ orderBy.rmsr = sort[0].name + ' ' + sort[0].dir;
+ }
+
+ return this.pcrud.search('rmsr', {id: this.bibIds}, {
+ order_by: orderBy,
+ limit: pager.limit,
+ offset: pager.offset,
+ flesh: 2,
+ flesh_fields: {
+ rmsr: ['biblio_record'],
+ bre: ['creator', 'editor']
+ }
+ });
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {BibListComponent} from './bib-list.component';
+
+@NgModule({
+ declarations: [
+ BibListComponent
+ ],
+ imports: [
+ StaffCommonModule
+ ],
+ exports: [
+ BibListComponent
+ ],
+ providers: [
+ ]
+})
+
+export class BibListModule {}
+
use OpenILS::Utils::Fieldmapper;
use OpenILS::Application::AppUtils;
+use MARC::Record;
+use MARC::File::XML (BinaryEncoding => 'UTF-8');
+use MARC::Charset;
use XML::LibXML;
use XML::LibXSLT;
use OpenILS::Utils::CStoreEditor q/:funcs/;
return $response;
}
+__PACKAGE__->register_method(
+ method => "authority_main_entry",
+ api_name => "open-ils.search.authority.main_entry",
+ stream => 1,
+ signature => {
+ desc => q/
+ Returns the main entry details for one or more authority
+ records plus a few other details.
+ /,
+ params => [
+ {desc => 'Authority IDs', type => 'number or array'}
+ ],
+ return => {
+ desc => q/
+ Stream of authority metadata objects.
+ { authority: are_object,
+ heading: heading_text,
+ thesaurus: short_code,
+ thesaurus_code: code,
+ control_set: control_set_object,
+ linked_bibs: [id1, id2, ...]
+ }
+ /,
+ type => 'object'
+ }
+ }
+);
+
+sub authority_main_entry {
+ my ($self, $client, $auth_ids) = @_;
+
+ $auth_ids = [$auth_ids] unless ref $auth_ids;
+
+ my $e = new_editor();
+
+ for my $auth_id (@$auth_ids) {
+
+ my $rec = $e->retrieve_authority_record_entry([
+ $auth_id, {
+ flesh => 1,
+ flesh_fields => {are => [qw/control_set bib_links creator/]}
+ }
+ ]) or return $e->event;
+
+ my $response = {
+ authority => $rec,
+ control_set => $rec->control_set,
+ linked_bibs => [ map {$_->bib} @{$rec->bib_links} ]
+ };
+
+ # Extract the heading and thesaurus.
+ # In theory this data has already been extracted in the DB, but
+ # using authority.simple_heading results in data that varies
+ # quite a bit from the previous authority manage interface. I
+ # took the MARC parsing approach because it matches the logic
+ # (and results) of the previous UI.
+
+ my $marc = MARC::Record->new_from_xml($rec->marc);
+ my $heading_field = $marc->field('1..');
+ $response->{heading} = $heading_field->as_string if $heading_field;
+
+ my $field_008 = $marc->field('008');
+ if ($field_008) {
+
+ # Extract the 1-char thesaurus code from the 008.
+ my $thes = substr($field_008->data, 11, 1);
+
+ if (defined $thes) {
+ $response->{thesaurus} = $thes;
+
+ if ($thes ne 'z') { # 'z' ('Other') maps to many entries
+ my $thesaurus = $e->search_authority_thesaurus(
+ {short_code => $thes})->[0];
+
+ $response->{thesaurus_code} = $thesaurus->code if $thesaurus;
+ }
+ }
+ }
+
+ $rec->clear_marc;
+ $client->respond($response);
+ }
+
+ return undef;
+}
+
1;
'Default org unit for patron search',
'cwst', 'label'
)
+), (
+ 'eg.grid.cat.authority.browse', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.authority.browse',
+ 'Grid Config: eg.grid.cat.authority.browse',
+ 'cwst', 'label')
+), (
+ 'eg.grid.cat.authority.manage.bibs', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.authority.manage.bibs',
+ 'Grid Config: eg.grid.cat.authority.manage.bibs',
+ 'cwst', 'label')
);
INSERT INTO config.workstation_setting_type
--- /dev/null
+BEGIN;
+
+-- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+ 'eg.grid.cat.authority.browse', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.authority.browse',
+ 'Grid Config: eg.grid.cat.authority.browse',
+ 'cwst', 'label')
+), (
+ 'eg.grid.cat.authority.manage.bibs', 'gui', 'object',
+ oils_i18n_gettext(
+ 'eg.grid.cat.authority.manage.bibs',
+ 'Grid Config: eg.grid.cat.authority.manage.bibs',
+ 'cwst', 'label')
+);
+
+COMMIT;
</li>
<li class="divider"></li>
<li>
- <a href="./cat/catalog/manageAuthorities" target="_self">
+ <a href="/eg2/staff/cat/authority/browse">
<span class="glyphicon glyphicon-lock" aria-hidden="true"></span>
[% l('Manage Authorities') %]
</a>
--- /dev/null
+Manage Authorities Angular Port
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+The Cataloging => 'Manage Authorities' interface has been ported to Angular.
+
+New functionality includes displaying additional authority data, like create
+and edit dates, etc. It's also possible to view the list of linked bib
+records.