LP1840050 org unit admin UI WIP
authorBill Erickson <berickxx@gmail.com>
Fri, 16 Aug 2019 16:48:55 +0000 (12:48 -0400)
committerBill Erickson <berickxx@gmail.com>
Fri, 16 Aug 2019 16:48:55 +0000 (12:48 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts
Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/server/org-addr.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/server/org-unit-routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.html
Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.component.ts
Open-ILS/src/eg2/src/app/staff/admin/server/org-unit.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts

index a7fe825..27a2c99 100644 (file)
@@ -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 (file)
index 0000000..0545025
--- /dev/null
@@ -0,0 +1,34 @@
+
+<ngb-tabset #addressTabs *ngIf="orgUnit">
+  <ng-container *ngFor="let type of addrTypes()">
+    <b>type = {{type}}</b>
+
+    <ngb-tab
+      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"
+          [showDelete]="true" (recordSaved)="addrSaved($event)" 
+          (recordDeleted)="addrDeleted($event)"
+          [record]="addr(type)"
+          fieldOrder="address_type,street1,street2,city,county,state,country,post_code,san,valid"
+          >
+        </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>
+
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 (file)
index 0000000..1bc2863
--- /dev/null
@@ -0,0 +1,113 @@
+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';
+
+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 {
+
+    private orgUnit: IdlObject = null;
+
+    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>();
+    }
+
+    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);
+                }
+            });
+        });
+    }
+
+    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.orgUnit);
+        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;
+    }
+
+    addrSaved(addr: number | IdlObject) {
+        const id = typeof addr === 'object' ? addr.id() : addr;
+
+        // TODO: update org if needed...
+    }
+
+    addrDeleted(addr: number | IdlObject) {
+        const id = typeof addr === 'object' ? addr.id() : addr;
+        // TODO: update 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 (file)
index 0000000..cbd3680
--- /dev/null
@@ -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 {}
index 8b600a9..ef8d360 100644 (file)
         [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>
index 6c86587..6df6379 100644 (file)
@@ -59,14 +59,16 @@ export class OrgUnitComponent implements OnInit {
     }
 
     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) { selectNodeId = this.org.root().id(); }
+
             const node = this.tree.findNode(selectNodeId);
             this.selected = node;
             this.tree.selectNode(node);
@@ -83,8 +85,6 @@ export class OrgUnitComponent implements OnInit {
                 this.generateHours(orgNode);
             }
 
-            this.addAddresses(orgNode);
-
             const treeNode = new TreeNode({
                 id: orgNode.id(),
                 label: orgNode.name(),
@@ -111,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());
@@ -271,5 +257,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 (file)
index 0000000..09150a8
--- /dev/null
@@ -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 {
+}
+
+
index aa4ec31..60bf2c9 100644 (file)
@@ -11,9 +11,9 @@ const routes: Routes = [{
 }, {
     path: 'actor/org_unit_type',
     component: OrgUnitTypeComponent
-}, {
+}, { 
     path: 'actor/org_unit',
-    component: OrgUnitComponent
+    loadChildren: '@eg/staff/admin/server/org-unit.module#OrgUnitModule'
 }, {
     path: ':schema/:table',
     component: BasicAdminPageComponent