// Allow the selected entry ID to be passed via the template
// This does NOT not emit onChange events.
@Input() set selectedId(id: any) {
- if (id) {
+ if (id) {
if (this.entrylist.length) {
this.selected = this.entrylist.filter(e => e.id === id)[0];
}
import {NgModule} from '@angular/core';
import {TreeModule} from '@eg/share/tree/tree.module';
-import {StaffCommonModule} from '@eg/staff/common.module';
-import {AdminServerRoutingModule} from './routing.module';
import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {AdminServerRoutingModule} from './routing.module';
import {AdminServerSplashComponent} from './admin-server-splash.component';
import {OrgUnitTypeComponent} from './org-unit-type.component';
-import {OrgUnitComponent} from './org-unit.component';
@NgModule({
declarations: [
AdminServerSplashComponent,
- OrgUnitTypeComponent,
- OrgUnitComponent
+ OrgUnitTypeComponent
],
imports: [
AdminCommonModule,
--- /dev/null
+
+<ngb-tabset #addressTabs *ngIf="orgUnit" (tabChange)="tabChanged($event)">
+ <ng-container *ngFor="let type of addrTypes()">
+ <b>type = {{type}}</b>
+
+ <ngb-tab *ngIf="addr(type)"
+ i18n-title id="{{type}}"
+ title="{{type === 'billing_address' ? 'Physical Address' :
+ (type === 'holds_address' ? 'Holds Address' :
+ (type === 'mailing_address' ? 'Mailing Address' : 'ILL Address'))}}">
+
+ <ng-template ngbTabContent>
+ <eg-fm-record-editor idlClass="aoa" readonlyFields="org_unit"
+ [mode]="addr(type).isnew() ? 'create': 'update'"
+ [hideBanner]="true" displayMode="inline" hiddenFields="id"
+ (recordSaved)="addrSaved($event)"
+ [record]="addr(type)"
+ fieldOrder="address_type,street1,street2,city,county,state,country,post_code,san,valid"
+ >
+ <eg-fm-record-editor-action i18n-label label="Delete" *ngIf="!addr(type).isnew()"
+ (actionClick)="deleteAddress($event)" buttonCss="btn-warning">
+ </eg-fm-record-editor-action>
+ </eg-fm-record-editor>
+
+ <ng-container *ngIf="sharedAddress(addr(type).id())">
+ <div class="alert alert-info">
+ <span i18n>This address is used for multiple address types.</span>
+ <button (click)="cloneAddress(type)"
+ class="btn btn-light ml-3" i18n>Clone As New Address</button>
+ </div>
+ </ng-container>
+ </ng-template>
+ </ngb-tab>
+ </ng-container>
+</ngb-tabset>
+
--- /dev/null
+import {Component, Input, Output, EventEmitter} from '@angular/core';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+
+const ADDR_TYPES =
+ ['billing_address', 'holds_address', 'mailing_address', 'ill_address'];
+
+@Component({
+ selector: 'eg-admin-org-address',
+ templateUrl: './org-addr.component.html'
+})
+export class OrgAddressComponent {
+
+ orgUnit: IdlObject = null;
+ private tabName: string;
+
+ private _orgId: number;
+
+ get orgId(): number { return this._orgId; }
+
+ @Input() set orgId(newId: number) {
+ if (newId) {
+ if (!this._orgId || this._orgId !== newId) {
+ this._orgId = newId;
+ this.init();
+ }
+ } else {
+ this._orgId = null;
+ }
+ }
+
+ @Output() addrChange: EventEmitter<IdlObject>;
+
+ constructor(
+ private idl: IdlService,
+ private org: OrgService,
+ private pcrud: PcrudService
+ ) {
+ this.addrChange = new EventEmitter<IdlObject>();
+ this.tabName = 'billing_address';
+ }
+
+ init() {
+ if (!this.orgId) { return; }
+
+ return this.pcrud.retrieve('aou', this.orgId,
+ {flesh : 1, flesh_fields : {aou : ADDR_TYPES}},
+ {authoritative: true}
+ ).subscribe(org => {
+ this.orgUnit = org;
+ ADDR_TYPES.forEach(aType => {
+ if (!this.addr(aType)) {
+ this.createAddress(aType);
+ }
+ });
+ });
+ }
+
+ tabChanged($event: NgbTabChangeEvent) {
+ this.tabName = $event.nextId;
+ }
+
+ addrTypes(): string[] { // for UI
+ return ADDR_TYPES;
+ }
+
+ // Template shorthand -- get a specific address by type.
+ addr(addrType: string) {
+ return this.orgUnit ? this.orgUnit[addrType]() : null;
+ }
+
+ createAddress(addrType: string) {
+ const addr = this.idl.create('aoa');
+ addr.isnew(true);
+ addr.valid('t');
+ addr.org_unit(this.orgId);
+ this.orgUnit[addrType](addr);
+ }
+
+ cloneAddress(addrType: string) {
+
+ // Find the address
+ let fromAddr: IdlObject;
+ ADDR_TYPES.forEach(aType => {
+ if (aType !== addrType &&
+ this.addr(aType).id() === this.addr(addrType).id()) {
+ fromAddr = this.addr(aType);
+ }
+ });
+
+ const addr = this.idl.clone(fromAddr);
+ addr.id(null);
+ addr.isnew(true);
+ addr.valid('t');
+ this.orgUnit[addrType](addr);
+ }
+
+ // True if the provided address is used for more than one addr type.
+ sharedAddress(addrId: number): boolean {
+ return ADDR_TYPES.filter(aType => {
+ return (
+ !this.addr(aType).isnew() && this.addr(aType).id() === addrId
+ );
+ }).length > 1;
+ }
+
+ deleteAddress($event: any) {
+ const addr = $event.record;
+ const tmpOrg = this.updatableOrg();
+
+ // Set the FKey to NULL on the org unit for deleted addresses
+ ADDR_TYPES.forEach(aType => {
+ const a = this.addr(aType);
+ if (a && a.id() === addr.id()) {
+ tmpOrg[aType](null);
+ this.createAddress(aType);
+ }
+ });
+
+ this.pcrud.update(tmpOrg).toPromise()
+ .then(_ => this.pcrud.remove(addr).toPromise())
+ .then(_ => this.addrChange.emit(addr));
+ }
+
+ // Addr saved by fm-editor.
+ // In the case of new address creation, point the org unit at
+ // the new address ID.
+ addrSaved(addr: number | IdlObject) {
+
+ if (typeof addr !== 'object') {
+ // pcrud returns a number on 'update' calls. No need to
+ // reload the data on a simple address change. it's changed
+ // in place.
+ return;
+ }
+
+ // update local copy with version that has an ID.
+ this.orgUnit[this.tabName](addr);
+
+ const org = this.updatableOrg();
+ org[this.tabName](addr.id());
+
+ // Creating a new address -- tell our org about it.
+ this.pcrud.update(org).toPromise().then(_ => this.addrChange.emit(addr));
+ }
+
+ // Create an unfleshed org unit object that's a clone of this.orgUnit
+ // to use when pushing updates to the server.
+ updatableOrg(): IdlObject {
+ const org = this.idl.clone(this.orgUnit);
+
+ ADDR_TYPES.forEach(aType => {
+ const addr = this.addr(aType);
+ if (addr) { org[aType](addr.id()); }
+ });
+
+ return org;
+ }
+
+}
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {OrgUnitComponent} from './org-unit.component';
+
+// Since org-unit admin has its own module with page-level components,
+// it needs its own routing module as well to define which component
+// to display at page load time.
+
+const routes: Routes = [{
+ path: '',
+ component: OrgUnitComponent
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class OrgUnitRoutingModule {}
</eg-confirm-dialog>
<ng-template #treeNodeLabelTmpl let-org="org">
- <span *ngIf="org" i18n>{{org.name()}} ({{org.shortname()}})</span>
+ <span *ngIf="org" i18n>{{org.name()}} -- {{org.shortname()}}</span>
</ng-template>
<eg-string #treeNodeLabel key='admin.server.org_unit.treenode'
[template]="treeNodeLabelTmpl"></eg-string>
</div>
<div class="col-lg-8">
<div class="alert alert-info">
- <div *ngIf="selected" i18n>
- {{currentOrg().name()}} ({{currentOrg().shortname()}})
+ <div *ngIf="currentOrg()">
+ <span *ngIf="currentOrg().name()" i18n>
+ {{currentOrg().name()}} ({{currentOrg().shortname()}})
+ </span>
+ <span *ngIf="!currentOrg().name()" class="font-italic" i18n>
+ Add Name
+ </span>
</div>
</div>
- <ngb-tabset #rootTabs (tabChange)="tabChanged($event)">
+ <ngb-tabset #rootTabs (tabChange)="tabChanged($event)" *ngIf="currentOrg()">
<ngb-tab title="Main Settings" i18n-title id="main">
<ng-template ngbTabContent>
<div class="mt-2">
<eg-fm-record-editor *ngIf="currentOrg()" #editDialog idlClass="aou"
[mode]="currentOrg().isnew() ? 'create': 'update'" [hideBanner]="true"
(recordSaved)="orgSaved($event)" displayMode="inline"
+ (recordDeleted)="orgDeleted()"
readonlyFields="parent,parent_ou" [preloadLinkedValues]="true"
[fieldOptions]="{ou_type: {customValues: orgTypeOptions()}}"
[record]="currentOrg().isnew() ? currentOrg() : null"
[recordId]="currentOrg().isnew() ? null : currentOrg().id()"
+ [showDelete]="!orgHasChildren()"
fieldOrder="parent_ou,ou_type,name,shortname,phone,email,opac_visible,fiscal_calendar"
hiddenFields="id,billing_address,mailing_address,holds_address,ill_address">
<eg-fm-record-editor-action label="Add Child" i18n-label
- [disabled]="orgChildTypes().length === 0"
+ [disabled]="orgChildTypes().length === 0 || currentOrg().isnew()"
+ buttonCss="btn-outline-info"
(actionClick)="addChild()"></eg-fm-record-editor-action>
</eg-fm-record-editor>
</div>
</ng-template>
</ngb-tab>
- <ngb-tab title="Hours of Operation" i18n-title id="hours">
+ <ngb-tab title="Hours of Operation" i18n-title id="hours"
+ [disabled]="currentOrg().isnew()">
<ng-template ngbTabContent>
<div class="mt-2 common-form striped-even">
<div class="row font-weight-bold mb-2">
</div>
</div>
<div class="row d-flex justify-content-end">
+ <div class="alert alert-warning mr-2 p-1"
+ *ngIf="currentOrg().hours_of_operation().isnew()">
+ Hours of Operation Have Not Yet Been Saved.
+ </div>
+ <div class="mr-2">
+ <button class="btn btn-warning" (click)="deleteHours()" i18n>
+ Clear Hours of Operation
+ </button>
+ </div>
<div>
<button class="btn btn-info" (click)="saveHours()" i18n>
Apply Changes
</div>
</ng-template>
</ngb-tab>
- <ngb-tab title="Addresses" i18n-title id="addresses">
+ <ngb-tab title="Addresses" i18n-title id="addresses"
+ [disabled]="currentOrg().isnew()">
<ng-template ngbTabContent>
<div class="mt-2">
- <ngb-tabset #addressTabs>
- <ngb-tab title="Physical Address" i18n-title id="physical">
- <ng-template ngbTabContent>
- <eg-fm-record-editor idlClass="aoa" readonlyFields="org_unit"
- [mode]="currentOrg().billing_address().isnew() ? 'create': 'update'"
- [hideBanner]="true" displayMode="inline" hiddenFields="id"
- (recordSaved)="orgSaved()"
- [recordId]="currentOrg().billing_address().isnew() ? null : currentOrg().billing_address().id()"
- [record]="currentOrg().billing_address().isnew() ? currentOrg().billing_address() : null"
- fieldOrder="address_type,street1,street2,city,county,state,country,post_code,san,valid"
- >
- </eg-fm-record-editor>
- </ng-template>
- </ngb-tab>
- <ngb-tab title="Holds Address" i18n-title id="holds">
- <ng-template ngbTabContent>
- <eg-fm-record-editor idlClass="aoa" readonlyFields="org_unit"
- [mode]="currentOrg().holds_address().isnew() ? 'create': 'update'"
- [hideBanner]="true" displayMode="inline" hiddenFields="id"
- (recordSaved)="orgSaved()"
- [recordId]="currentOrg().holds_address().isnew() ? null : currentOrg().holds_address().id()"
- [record]="currentOrg().holds_address().isnew() ? currentOrg().holds_address() : null"
- fieldOrder="address_type,street1,street2,city,county,state,country,post_code,san,valid"
- >
- </eg-fm-record-editor>
- </ng-template>
- </ngb-tab>
- <ngb-tab title="Mailing Address" i18n-title id="mailing">
- <ng-template ngbTabContent>
- <eg-fm-record-editor idlClass="aoa" readonlyFields="org_unit"
- [mode]="currentOrg().mailing_address().isnew() ? 'create': 'update'"
- [hideBanner]="true" displayMode="inline" hiddenFields="id"
- (recordSaved)="orgSaved()"
- [recordId]="currentOrg().mailing_address().isnew() ? null : currentOrg().mailing_address().id()"
- [record]="currentOrg().mailing_address().isnew() ? currentOrg().mailing_address() : null"
- fieldOrder="address_type,street1,street2,city,county,state,country,post_code,san,valid"
- >
- </eg-fm-record-editor>
- </ng-template>
- </ngb-tab>
- <ngb-tab title="ILL Address" i18n-title id="ill">
- <ng-template ngbTabContent>
- <eg-fm-record-editor idlClass="aoa" readonlyFields="org_unit"
- [mode]="currentOrg().ill_address().isnew() ? 'create': 'update'"
- [hideBanner]="true" displayMode="inline" hiddenFields="id"
- (recordSaved)="orgSaved()"
- [recordId]="currentOrg().ill_address().isnew() ? null : currentOrg().ill_address().id()"
- [record]="currentOrg().ill_address().isnew() ? currentOrg().ill_address() : null"
- fieldOrder="address_type,street1,street2,city,county,state,country,post_code,san,valid"
- >
- </eg-fm-record-editor>
- </ng-template>
- </ngb-tab>
- </ngb-tabset>
+ <eg-admin-org-address [orgId]="currentOrg().id()" (addrChange)="addressChanged($event)">
+ </eg-admin-org-address>
</div>
</ng-template>
</ngb-tab>
// stubbing out in case we need it.
}
- orgSaved(orgId: number) {
- if (!orgId && this.currentOrg()) {
- orgId = this.currentOrg().id();
+ orgSaved(orgId: number | IdlObject) {
+ let id;
+
+ if (orgId) { // new org created, focus it.
+ id = typeof orgId === 'object' ? orgId.id() : orgId;
+ } else if (this.currentOrg()) {
+ id = this.currentOrg().id();
}
- this.loadAouTree(orgId).then(_ => this.postUpdate(this.editString));
+
+ this.loadAouTree(id).then(_ => this.postUpdate(this.editString));
+ }
+
+ orgDeleted() {
+ this.loadAouTree();
}
loadAouTree(selectNodeId?: number): Promise<any> {
+
+ const flesh = ['children', 'ou_type', 'hours_of_operation'];
+
return this.pcrud.search('aou', {parent_ou : null},
- {flesh : -1, flesh_fields : {aou : [
- 'children', 'ou_type', 'hours_of_operation', 'ill_address',
- 'holds_address', 'mailing_address', 'billing_address'
- ]}}, {authoritative: true}
+ {flesh : -1, flesh_fields : {aou : flesh}}, {authoritative: true}
+
).toPromise().then(tree => {
this.ingestAouTree(tree);
- if (selectNodeId) {
- const node = this.tree.findNode(selectNodeId);
- this.selected = node;
- this.tree.selectNode(node);
- }
+ if (!selectNodeId) { selectNodeId = this.org.root().id(); }
+
+ const node = this.tree.findNode(selectNodeId);
+ this.selected = node;
+ this.tree.selectNode(node);
});
}
this.generateHours(orgNode);
}
- this.addAddresses(orgNode);
-
const treeNode = new TreeNode({
id: orgNode.id(),
label: orgNode.name(),
this.selected = $event;
}
- // Fills the org unit in with 'new' addresses for any fields
- // that are missing.
- addAddresses(org: IdlObject) {
- ['billing_address', 'mailing_address', 'holds_address', 'ill_address']
- .forEach(addrType => {
- if (org[addrType]()) { return ; }
- const addr = this.idl.create('aoa');
- addr.isnew(true);
- addr.valid('t');
- addr.org_unit(org.id());
- org[addrType](addr);
- });
- }
-
generateHours(org: IdlObject) {
const hours = this.idl.create('aouhoo');
hours.id(org.id());
);
}
+ deleteHours() {
+ const hours = this.currentOrg().hours_of_operation();
+ const promise = hours.isnew() ? Promise.resolve() :
+ this.pcrud.remove(hours).toPromise();
+
+ promise.then(_ => this.generateHours(this.currentOrg()));
+ }
+
currentOrg(): IdlObject {
return this.selected ? this.selected.callerData.orgUnit : null;
}
+ orgHasChildren(): boolean {
+ const org = this.currentOrg();
+ return (org && org.children().length > 0);
+ }
+
postUpdate(message: StringComponent) {
// Modifying org unit types means refetching the org unit
// data normally fetched on page load, since it includes
addChild() {
const parentTreeNode = this.selected;
- const parentOrg = parentTreeNode.callerData.orgUnit;
+ const parentOrg = this.currentOrg();
const newType = this.orgChildTypes()[0];
const org = this.idl.create('aou');
org.isnew(true);
org.parent_ou(parentOrg.id());
org.ou_type(newType.id());
+ org.children([]);
// Create a dummy, detached org node to keep the UI happy.
this.selected = new TreeNode({
callerData: {orgUnit: org}
});
}
+
+ addressChanged(thing: any) {
+ // Reload to pick up org unit address changes.
+ this.orgSaved(this.currentOrg().id());
+ }
}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {TreeModule} from '@eg/share/tree/tree.module';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {OrgUnitComponent} from './org-unit.component';
+import {OrgAddressComponent} from './org-addr.component';
+import {OrgUnitRoutingModule} from './org-unit-routing.module';
+
+@NgModule({
+ declarations: [
+ OrgUnitComponent,
+ OrgAddressComponent
+ ],
+ imports: [
+ AdminCommonModule,
+ OrgUnitRoutingModule,
+ TreeModule
+ ],
+ exports: [
+ ],
+ providers: [
+ ]
+})
+
+export class OrgUnitModule {
+}
+
+
component: OrgUnitTypeComponent
}, {
path: 'actor/org_unit',
- component: OrgUnitComponent
+ loadChildren: '@eg/staff/admin/server/org-unit.module#OrgUnitModule'
}, {
path: ':schema/:table',
component: BasicAdminPageComponent