LPXXX Angular Volcopy
authorBill Erickson <berickxx@gmail.com>
Thu, 4 Jun 2020 20:18:31 +0000 (16:18 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 4 Jun 2020 20:18:31 +0000 (16:18 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
12 files changed:
Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts
Open-ILS/src/eg2/src/app/share/combobox/combobox.component.html
Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/routing.module.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.html
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.module.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.ts
Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts
Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.service.ts

index c80d0b2..39126c2 100644 (file)
@@ -361,7 +361,7 @@ export class CatalogService {
     }
 
     iconFormatLabel(code: string): string {
-        if (this.ccvmMap) {
+        if (this.ccvmMap && this.ccvmMap.icon_format) {
             const ccvm = this.ccvmMap.icon_format.filter(
                 format => format.code() === code)[0];
             if (ccvm) {
index 83df22e..c456f80 100644 (file)
@@ -7,7 +7,10 @@
 <div class="d-flex">
   <input type="text" 
     class="form-control"
-    [ngClass]="{'text-success font-italic font-weight-bold': selected && selected.freetext}"
+    [ngClass]="{
+      'text-success font-italic font-weight-bold': selected && selected.freetext,
+      'form-control-sm': smallFormControl
+    }"
     [placeholder]="placeholder"
     [name]="name"
     [disabled]="isDisabled"
index 41d2b30..d7f58f6 100644 (file)
@@ -54,6 +54,9 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit {
 
     @Input() inputSize: number = null;
 
+    // If true, applies form-control-sm CSS
+    @Input() smallFormControl = false;
+
     // Add a 'required' attribute to the input
     isRequired: boolean;
     @Input() set required(r: boolean) {
index edaf0d7..368c30a 100644 (file)
@@ -3,7 +3,7 @@ import {RouterModule, Routes} from '@angular/router';
 import {VolCopyComponent} from './volcopy.component';
 
 const routes: Routes = [{
-    path: 'edit/item/:item_id',
+    path: 'edit/item/:copy_id',
     component: VolCopyComponent
   }, {
     path: 'edit/session/:session',
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html
new file mode 100644 (file)
index 0000000..664d2e1
--- /dev/null
@@ -0,0 +1,93 @@
+
+<div class="row bg-info">
+  <b>BATCH</b>
+</div>
+
+<div class="row d-flex mt-2 mb-2">
+  <div class="p-1" [ngStyle]="{flex: flexAt(1)}">
+    <label class="font-weight-bold" i18n>Owning Library</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(2)}">
+    <label class="font-weight-bold" i18n>Call Numbers</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(3)}">
+    <label class="font-weight-bold" i18n>Classification</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(4)}">
+    <label class="font-weight-bold" i18n>Prefix</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(5)}">
+    <label class="font-weight-bold" i18n>Call Number Label</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(6)}">
+    <label class="font-weight-bold" i18n>Suffix</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(7)}">
+    <label class="font-weight-bold" i18n>Items</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(8)}">
+    <label class="font-weight-bold" i18n>Barcode</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(9)}">
+    <label class="font-weight-bold" i18n>Item #</label>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(10)}">
+    <label class="font-weight-bold" i18n>Part</label>
+  </div>
+</div>
+
+
+<ng-container *ngFor="let orgNode of context.orgNodes(); let orgIdx = index">
+  <ng-container *ngFor="let volNode of orgNode.children; let volIdx = index">
+    <ng-container *ngFor="let copyNode of volNode.children; let copyIdx = index">
+      <div class="row d-flex mt-1">
+        <div class="p-1" [ngStyle]="{flex: flexAt(1)}">
+          <span *ngIf="volIdx == 0">{{orgNode.target.shortname()}}</span>
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(2)}">
+          <ng-container *ngIf="volIdx == 0">
+            <input type="number" class="form-control form-control-sm"
+              [ngModel]="orgNode.children.length"
+              (ngModelChange)="volCountChanged(orgNode, $event)"/>
+          </ng-container>
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(3)}">
+          <ng-container *ngIf="copyIdx == 0">
+            <eg-combobox 
+              [entries]="volClasses" 
+              [smallFormControl]="true"
+              [required]="true" 
+              (onChange)="volClassChanged(volNode, $event)">
+            </eg-combobox>
+          </ng-container>
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(4)}">
+          <ng-container *ngIf="copyIdx == 0">
+            <eg-combobox 
+              [smallFormControl]="true"
+              (onChange)="volPrefixChanged(volNode, $event)">
+              <eg-combobox-entry entryId="-1" entryLabel="<Unset>" 
+                i18n-entryLabel></eg-combobox-entry>
+              <eg-combobox-entry *ngFor="let pfx of volPrefixes" 
+                [entryId]="pfx.id()" [entryLabel]="pfx.label()">
+              </eg-combobox-entry>
+            </eg-combobox>
+          </ng-container>
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(5)}">
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(6)}">
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(7)}">
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(8)}">
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(9)}">
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(10)}">
+        </div>
+      </div>
+    </ng-container>
+  </ng-container>
+</ng-container>
+
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts
new file mode 100644 (file)
index 0000000..a39f5f4
--- /dev/null
@@ -0,0 +1,82 @@
+import {Component, OnInit, AfterViewInit, ViewChild, Input, Renderer2} from '@angular/core';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {VolCopyContext, HoldingsTreeNode} from './volcopy';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
+
+@Component({
+  selector: 'eg-vol-edit',
+  templateUrl: 'vol-edit.component.html'
+})
+
+
+export class VolEditComponent implements OnInit {
+
+    @Input() context: VolCopyContext;
+
+    // There are 10 columns in the editor form.  Set the flex values
+    // here so they don't have to be hard-coded and repeated in the
+    // markup.  Changing a flex value here will propagate to all
+    // rows in the form.
+    flexSettings: {[column: number]: number} = {
+        1: 1, 2: 1, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 1, 10: 1};
+
+    volClasses: IdlObject[] = null;
+    volPrefixes: IdlObject[] = null;
+    volSuffixes: IdlObject[] = null;
+
+    constructor(
+        private holdings: HoldingsService
+    ) {}
+
+    ngOnInit() {
+
+        // TODO: Filter these to only show org-scoped values
+        // plus any values otherwise needed for the current
+        // holdings tree.
+
+        this.holdings.fetchCallNumberClasses().then(
+            classes => this.volClasses = classes);
+
+        this.holdings.fetchCallNumberPrefixes().then(prefixes => {
+            this.volPrefixes = prefixes.filter(pfx => pfx.id() !== -1)
+        });
+
+        this.holdings.fetchCallNumberSuffixes().then(suffixes =>
+            this.volSuffixes = suffixes.filter(pfx => pfx.id() !== -1));
+    }
+
+    flexAt(column: number): number {
+        return this.flexSettings[column];
+    }
+
+    volCountChanged(orgNode: HoldingsTreeNode, value: number) {
+        console.log('vol set set to ', value);
+    }
+
+    applyVolValue(vol: IdlObject, key: string, value: any) {
+        if (vol[key]() !== value) {
+            vol[key](value);
+            vol.ischanged(true);
+        }
+    }
+
+    volClassChanged(volNode: HoldingsTreeNode, entry: ComboboxEntry) {
+        if (!entry) { return; }
+        this.applyVolValue(volNode.target, 'label_class', entry.id);
+    }
+
+    volPrefixChanged(volNode: HoldingsTreeNode, entry: ComboboxEntry) {
+        if (!entry) { return; }
+        this.applyVolValue(volNode.target, 'prefix', entry.id);
+    }
+
+    volSuffixChanged(volNode: HoldingsTreeNode, entry: ComboboxEntry) {
+        if (!entry) { return; }
+        this.applyVolValue(volNode.target, 'suffix', entry.id);
+    }
+}
+
index 0945cf3..df54f57 100644 (file)
@@ -1,2 +1,11 @@
+<eg-staff-banner bannerText="Holdings Editor" i18n-bannerText></eg-staff-banner>
 
-VOLCOPY
+<ng-container *ngIf="!loading">
+
+  <eg-bib-summary *ngIf="recordId" [recordId]="recordId"></eg-bib-summary>
+  
+  <div class="mt-3">
+    <eg-vol-edit [context]="context"></eg-vol-edit>
+  </div>
+
+</ng-container>
index 36c2b67..696406b 100644 (file)
@@ -3,9 +3,10 @@ import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {IdlObject} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 import {PcrudService} from '@eg/core/pcrud.service';
+import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
 import {VolCopyContext} from './volcopy';
 
-const ITEM_FLESH = {
+const copy_FLESH = {
     flesh: 1,
     flesh_fields: {
         acp: ['call_number', 'location']
@@ -18,16 +19,18 @@ const ITEM_FLESH = {
 export class VolCopyComponent implements OnInit {
 
     context: VolCopyContext;
-    itemId: number;
+    recordId: number;
+    copyId: number;
     session: string;
-    loading = false;
+    loading = true;
 
     constructor(
         private router: Router,
         private route: ActivatedRoute,
         private renderer: Renderer2,
         private org: OrgService,
-        private pcrud: PcrudService
+        private pcrud: PcrudService,
+        private holdings: HoldingsService
     ) { }
 
     ngOnInit() {
@@ -39,14 +42,14 @@ export class VolCopyComponent implements OnInit {
     }
 
     negotiateRoute(params: ParamMap) {
-        const itemId = +params.get('item_id');
-        if (itemId) {
-            if (itemId !== this.itemId) {
-                this.itemId = itemId;
+        const copyId = +params.get('copy_id');
+        if (copyId) {
+            if (copyId !== this.copyId) {
+                this.copyId = copyId;
                 this.load();
             }
         } else {
-            this.itemId = null;
+            this.copyId = null;
         }
     }
 
@@ -54,19 +57,24 @@ export class VolCopyComponent implements OnInit {
         this.loading = true;
         this.context.reset();
         this.fetchHoldings()
+        .then(_ => this.holdings.fetchCallNumberClasses())
+        .then(_ => this.holdings.fetchCallNumberPrefixes())
+        .then(_ => this.holdings.fetchCallNumberSuffixes())
+        .then(_ => this.context.sortHoldings())
         .then(_ => this.loading = false);
     }
 
     fetchHoldings(): Promise<any> {
-        if (this.itemId) {
-            return this.fetchItem();
+        if (this.copyId) {
+            return this.fetchCopy();
         }
     }
 
-    fetchItem(): Promise<any> {
-        return this.pcrud.retrieve('acp', this.itemId, ITEM_FLESH)
-        .toPromise().then(item => {
-            this.context.findOrCreateItemNode(item);
+    fetchCopy(): Promise<any> {
+        return this.pcrud.retrieve('acp', this.copyId, copy_FLESH)
+        .toPromise().then(copy => {
+            this.recordId = copy.call_number().record();
+            this.context.findOrCreateCopyNode(copy);
         });
     }
 }
index 3e31b8a..cdd9e19 100644 (file)
@@ -4,10 +4,12 @@ import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
 import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
 import {VolCopyRoutingModule} from './routing.module';
 import {VolCopyComponent} from './volcopy.component';
+import {VolEditComponent} from './vol-edit.component';
 
 @NgModule({
   declarations: [
-    VolCopyComponent
+    VolCopyComponent,
+    VolEditComponent
   ],
   imports: [
     StaffCommonModule,
index 5824d1d..4d5f799 100644 (file)
@@ -1,9 +1,9 @@
 import {IdlObject} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 
-class HoldingsTreeNode {
+export class HoldingsTreeNode {
     children: HoldingsTreeNode[];
-    nodeType: 'org' | 'callNum' | 'item';
+    nodeType: 'org' | 'vol' | 'copy';
     target: any;
     parentNode: HoldingsTreeNode;
     constructor() {
@@ -50,18 +50,18 @@ export class VolCopyContext {
         return node;
     }
 
-    findOrCreateCallNumNode(callNum: IdlObject): HoldingsTreeNode {
-        const orgId = callNum.owning_lib();
+    findOrCreateCallNumNode(vol: IdlObject): HoldingsTreeNode {
+        const orgId = vol.owning_lib();
         const orgNode = this.findOrCreateOrgNode(orgId);
 
         const existing = orgNode.children.filter(
-            n => n.target.id() === callNum.id())[0];
+            n => n.target.id() === vol.id())[0];
 
         if (existing) { return existing; }
 
         const node: HoldingsTreeNode = new HoldingsTreeNode();
-        node.nodeType = 'callNum';
-        node.target = callNum;
+        node.nodeType = 'vol';
+        node.target = vol;
         node.parentNode = orgNode;
 
         orgNode.children.push(node);
@@ -70,21 +70,21 @@ export class VolCopyContext {
     }
 
 
-    findOrCreateItemNode(item: IdlObject): HoldingsTreeNode {
+    findOrCreateCopyNode(copy: IdlObject): HoldingsTreeNode {
 
-        const callNumNode = this.findOrCreateCallNumNode(item.call_number());
+        const volNode = this.findOrCreateCallNumNode(copy.call_number());
 
-        const existing = callNumNode.children.filter(
-            c => c.target.id() === item.id())[0];
+        const existing = volNode.children.filter(
+            c => c.target.id() === copy.id())[0];
 
         if (existing) { return existing; }
 
         const node: HoldingsTreeNode = new HoldingsTreeNode();
-        node.nodeType = 'item';
-        node.target = item.
-        node.parentNode = callNumNode;
+        node.nodeType = 'copy';
+        node.target = copy;
+        node.parentNode = volNode;
 
-        callNumNode.children.push(node);
+        volNode.children.push(node);
 
         return node;
     }
@@ -93,10 +93,10 @@ export class VolCopyContext {
     sortHoldings() {
 
         this.orgNodes().forEach(orgNode => {
-            orgNode.children.forEach(callNumNode => {
+            orgNode.children.forEach(volNode => {
 
-                // Sort items by barcode code
-                callNumNode.children = callNumNode.children.sort((c1, c2) =>
+                // Sort copys by barcode code
+                volNode.children = volNode.children.sort((c1, c2) =>
                     c1.target.barcode() < c2.target.barcode() ? -1 : 1);
 
             });
@@ -106,9 +106,26 @@ export class VolCopyContext {
                 c1.target.label() < c2.target.label() ? -1 : 1);
         });
 
-
         // sort org units by shortname
         this.holdings.root.children = this.orgNodes().sort((o1, o2) =>
             o1.target.shortname() < o2.target.shortname() ? -1 : 1);
     }
+
+    // Sorted list of holdings tree nodes
+    /*
+    flattenHoldings(): HoldingsTreeNode[] {
+        this.sortHoldings();
+        let nodes: HoldingsTreeNode[] = [];
+
+        this.orgNodes().forEach(orgNode => {
+            nodes.push(orgNode);
+            orgNode.children.forEach(volNode => {
+                nodes.push(volNode);
+                nodes = nodes.concat(volNode.children);
+            });
+        });
+
+        return nodes;
+    }
+    */
 }
index 39b8944..4b1a4b8 100644 (file)
@@ -45,30 +45,30 @@ export class BibSummaryComponent implements OnInit {
 
     ngOnInit() {
 
-        if (this.summary) {
-            this.summary.getBibCallNumber();
-        } else {
-            if (this.recordId) {
-                this.loadSummary();
-            }
-        }
-
         this.store.getItem('eg.cat.record.summary.collapse')
         .then(value => this.expand = !value)
-        .then(() => this.initDone = true);
+        .then(_ => this.cat.fetchCcvms())
+        .then(_ => {
+            if (this.summary) {
+                return this.summary.getBibCallNumber();
+            } else {
+                if (this.recordId) {
+                    return this.loadSummary();
+                }
+            }
+        }).then(_ => this.initDone = true);
     }
 
     saveExpandState() {
         this.store.setItem('eg.cat.record.summary.collapse', !this.expand);
     }
 
-    loadSummary(): void {
-        this.bib.getBibSummary(this.recordId).toPromise()
+    loadSummary(): Promise<any> {
+        return this.bib.getBibSummary(this.recordId).toPromise()
         .then(summary => {
-            summary.getBibCallNumber();
-            this.bib.fleshBibUsers([summary.record]);
             this.summary = summary;
-        });
+            return summary.getBibCallNumber();
+        }).then(_ => this.bib.fleshBibUsers([this.summary.record]));
     }
 
     orgName(orgId: number): string {
index 5c91a68..7597020 100644 (file)
@@ -2,10 +2,12 @@
  * Common code for mananging holdings
  */
 import {Injectable, EventEmitter} from '@angular/core';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {AnonCacheService} from '@eg/share/util/anon-cache.service';
 import {AuthService} from '@eg/core/auth.service';
 import {EventService} from '@eg/core/event.service';
+import {PcrudService} from '@eg/core/pcrud.service';
 
 interface NewCallNumData {
     owner?: number;
@@ -17,9 +19,14 @@ interface NewCallNumData {
 @Injectable()
 export class HoldingsService {
 
+    callNumberClasses: IdlObject[];
+    callNumberPrefixes: IdlObject[];
+    callNumberSuffixes: IdlObject[];
+
     constructor(
         private net: NetService,
         private auth: AuthService,
+        private pcrud: PcrudService,
         private evt: EventService,
         private anonCache: AnonCacheService
     ) {}
@@ -57,5 +64,50 @@ export class HoldingsService {
             });
         });
     }
+
+    // Returns a sorted list of call number classes
+    fetchCallNumberClasses(): Promise<IdlObject[]> {
+        if (this.callNumberClasses) {
+            return Promise.resolve(this.callNumberClasses);
+        }
+
+        return this.pcrud.retrieveAll('acnc', {}, {atomic: true})
+        .toPromise().then(classes => {
+            this.callNumberClasses = classes.sort(
+                (c1, c2) => c1.name() < c2.name() ? -1 : 1);
+            return this.callNumberClasses;
+        });
+    }
+
+    // Returns a sorted list of call number prefixes
+    fetchCallNumberPrefixes(): Promise<IdlObject[]> {
+        if (this.callNumberPrefixes) {
+            console.log('H2', this.callNumberPrefixes);
+            return Promise.resolve(this.callNumberPrefixes);
+        }
+
+        return this.pcrud.retrieveAll('acnp', {}, {atomic: true})
+        .toPromise().then(prefixes => {
+            this.callNumberPrefixes = prefixes.sort(
+                (c1, c2) => c1.label_sortkey() < c2.label_sortkey() ? -1 : 1);
+            console.log('H1', this.callNumberPrefixes);
+            return this.callNumberPrefixes;
+        });
+    }
+
+    // Returns a sorted list of call number suffixes
+    fetchCallNumberSuffixes(): Promise<IdlObject[]> {
+        if (this.callNumberSuffixes) {
+            return Promise.resolve(this.callNumberSuffixes);
+        }
+
+        return this.pcrud.retrieveAll('acns', {}, {atomic: true})
+        .toPromise().then(suffixes => {
+            this.callNumberSuffixes = suffixes.sort(
+                (c1, c2) => c1.label_sortkey() < c2.label_sortkey() ? -1 : 1);
+            return this.callNumberSuffixes;
+        });
+    }
+
 }