From b9da17a17370e2395193a92a6efa622e30a0cb36 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Thu, 15 Aug 2019 11:40:23 -0400 Subject: [PATCH] LP1840050 org unit admin UI WIP Signed-off-by: Bill Erickson --- .../src/app/share/combobox/combobox.component.ts | 2 +- .../app/staff/admin/server/admin-server.module.ts | 7 +- .../app/staff/admin/server/org-addr.component.html | 36 +++++ .../app/staff/admin/server/org-addr.component.ts | 163 +++++++++++++++++++++ .../staff/admin/server/org-unit-routing.module.ts | 19 +++ .../app/staff/admin/server/org-unit.component.html | 89 ++++------- .../app/staff/admin/server/org-unit.component.ts | 73 +++++---- .../src/app/staff/admin/server/org-unit.module.ts | 27 ++++ .../src/app/staff/admin/server/routing.module.ts | 2 +- 9 files changed, 320 insertions(+), 98 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/org-unit-routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.module.ts diff --git a/Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts b/Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts index 9e7dc25f89..7edfdf4f10 100644 --- a/Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts +++ b/Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts @@ -72,7 +72,7 @@ export class ComboboxComponent implements ControlValueAccessor, OnInit { // 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]; } diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts index a7fe8250fd..27a2c99810 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts @@ -1,17 +1,14 @@ 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, diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.html b/Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.html new file mode 100644 index 0000000000..b3a787b5f4 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.html @@ -0,0 +1,36 @@ + + + + type = {{type}} + + + + + + + + + + +
+ This address is used for multiple address types. + +
+
+
+
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.ts new file mode 100644 index 0000000000..2092075104 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.ts @@ -0,0 +1,163 @@ +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; + + constructor( + private idl: IdlService, + private org: OrgService, + private pcrud: PcrudService + ) { + this.addrChange = new EventEmitter(); + 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; + } + +} + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit-routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit-routing.module.ts new file mode 100644 index 0000000000..cbd36804f8 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit-routing.module.ts @@ -0,0 +1,19 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.html b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.html index 4389217eb8..294d62c2bf 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.html +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.html @@ -14,7 +14,7 @@ - {{org.name()}} ({{org.shortname()}}) + {{org.name()}} -- {{org.shortname()}} @@ -26,31 +26,40 @@
-
- {{currentOrg().name()}} ({{currentOrg().shortname()}}) +
+ + {{currentOrg().name()}} ({{currentOrg().shortname()}}) + + + Add Name +
- +
- +
@@ -83,6 +92,15 @@
+
+ Hours of Operation Have Not Yet Been Saved. +
+
+ +
- +
- - - - - - - - - - - - - - - - - - - - - - - - - - + +
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.ts index e2ade99955..714d7b7e51 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.ts @@ -42,26 +42,36 @@ export class OrgUnitComponent implements OnInit { // 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 { + + 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); }); } @@ -75,8 +85,6 @@ export class OrgUnitComponent implements OnInit { this.generateHours(orgNode); } - this.addAddresses(orgNode); - const treeNode = new TreeNode({ id: orgNode.id(), label: orgNode.name(), @@ -103,20 +111,6 @@ export class OrgUnitComponent implements OnInit { 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()); @@ -175,10 +169,23 @@ export class OrgUnitComponent implements OnInit { ); } + 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 @@ -242,13 +249,14 @@ export class OrgUnitComponent implements OnInit { 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({ @@ -257,5 +265,10 @@ export class OrgUnitComponent implements OnInit { callerData: {orgUnit: org} }); } + + addressChanged(thing: any) { + // Reload to pick up org unit address changes. + this.orgSaved(this.currentOrg().id()); + } } diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.module.ts new file mode 100644 index 0000000000..09150a803a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.module.ts @@ -0,0 +1,27 @@ +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 { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts index aa4ec3184b..3c076551b1 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts @@ -13,7 +13,7 @@ const routes: Routes = [{ component: OrgUnitTypeComponent }, { path: 'actor/org_unit', - component: OrgUnitComponent + loadChildren: '@eg/staff/admin/server/org-unit.module#OrgUnitModule' }, { path: ':schema/:table', component: BasicAdminPageComponent -- 2.11.0