funds: add fund transfer modal
authorGalen Charlton <gmc@equinoxinitiative.org>
Sun, 28 Mar 2021 22:37:59 +0000 (18:37 -0400)
committerGalen Charlton <gmc@equinoxinitiative.org>
Sun, 28 Mar 2021 22:37:59 +0000 (18:37 -0400)
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-details-dialog.component.html
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-details-dialog.component.ts
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-transfer-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-transfer-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/funds/funds.module.ts

index 1ee4a0f..98a8c3a 100644 (file)
@@ -13,7 +13,7 @@
 </ng-template>
 <ng-template #dialogContent>
   <div class="modal-header bg-info">
-    <h3 class="modal-title" i18n>Fund Details - {{fund?.name()}} ({{fund?.code()}} {{fund?.year()}})</h3>
+    <h3 class="modal-title" i18n>Fund Details - {{fund?.name()}} ({{fund?.code()}} ({{fund?.year()}}) ({{fund?.org().shortname()}}))</h3>
     <button type="button" class="close"
       i18n-aria-label aria-label="Close" (click)="close()">
       <span aria-hidden="true">&times;</span>
   </div>
   <div class="modal-body">
     <div class="row mt-3">
-      <div class="col-lg-12 text-right pb-1">
-        <button class="btn btn-secondary btn-sm" [disabled]="activeTab == defaultTabType"
+      <div class="col-lg-3">
+        <button class="btn btn-primary"
+          (click)="allocateToFund()" i18n>Create Allocation</button>
+        <button class="btn btn-primary ml-1"
+          (click)="doTransfer()" i18n>Transfer Money</button>
+      </div>
+      <div class="col-lg-9 text-right pb-1">
+        <button class="btn btn-secondary btn-xs" [disabled]="activeTab == defaultTabType"
           (click)="setDefaultTab()" i18n>Set Default View</button>
       </div>
     </div>
     [preloadLinkedValues]="true"
     [readonlyFields]="readonlyFields">
 </eg-fm-record-editor>
+
+<eg-fm-record-editor #allocateToFundDialog idlClass="acqfa">
+</eg-fm-record-editor>
+
+<eg-fund-transfer-dialog #transferDialog></eg-fund-transfer-dialog>
index 0c985db..f6efc48 100644 (file)
@@ -14,6 +14,7 @@ import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
 import {StringComponent} from '@eg/share/string/string.component';
 import {ToastService} from '@eg/share/toast/toast.service';
 import {FundTagsComponent} from './fund-tags.component';
+import {FundTransferDialogComponent} from './fund-transfer-dialog.component';
 
 @Component({
   selector: 'eg-fund-details-dialog',
@@ -32,6 +33,8 @@ export class FundDetailsDialogComponent
     acqfdebDataSource: GridDataSource
 
     @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+    @ViewChild('transferDialog', { static: false }) transferDialog: FundTransferDialogComponent;
+    @ViewChild('allocateToFundDialog', { static: true }) allocateToFundDialog: FmRecordEditorComponent;
     @ViewChild('successString', { static: true }) successString: StringComponent;
     @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent;
 
@@ -158,6 +161,35 @@ export class FundDetailsDialogComponent
         );
     }
 
+    allocateToFund() {
+        const allocation = this.idl.create('acqfa');
+        allocation.fund(this.fundId);
+        allocation.allocator(this.auth.user().id());
+        this.allocateToFundDialog.defaultNewRecord = allocation;
+        this.allocateToFundDialog.mode = 'create';
+
+        this.allocateToFundDialog.hiddenFieldsList = ['id', 'fund', 'allocator', 'create_time'];
+        this.allocateToFundDialog.fieldOrder = 'funding_source,amount,note';
+        this.allocateToFundDialog.open().subscribe(
+             result => {
+                this.successString.current()
+                    .then(str => this.toast.success(str));
+                this._initRecord();
+            },
+            error => {
+                this.updateFailedString.current()
+                    .then(str => this.toast.danger(str));
+            }
+        );
+    }
+
+    doTransfer() {
+        this.transferDialog.sourceFund = this.fund;
+        this.transferDialog.open().subscribe(
+            ok => this._initRecord()
+        )
+    }
+
     setDefaultTab() {
         this.defaultTabType = this.activeTab;
         this.store.setLocalItem('eg.acq.fund_details.default_tab', this.activeTab);
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-transfer-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-transfer-dialog.component.html
new file mode 100644 (file)
index 0000000..fa9b6c9
--- /dev/null
@@ -0,0 +1,50 @@
+<eg-string #successString i18n-text text="Fund Transfer Succeeded"></eg-string>
+<eg-string #updateFailedString i18n-text text="Fund Transfer Failed"></eg-string>
+
+<ng-template #dialogContent>
+  <div class="modal-header bg-info" *ngIf="doneLoading">
+    <h3 class="modal-title" i18n>Transfer from Fund {{sourceFund?.name()}} ({{sourceFund?.code()}} ({{sourceFund?.year()}}) ({{sourceFund?.org().shortname()}}))</h3>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body" [hidden]="!doneLoading">
+    <form #xfrForm="ngForm" role="form" class="form-validated">
+      <div class="form-group row mt-2">
+        <label for="dest-fund" class="col-sm-4 col-form-label" i18n>Destination Fund</label>
+        <div class="col-sm-8">
+          <eg-combobox #fundSelector [asyncSupportsEmptyTermClick]="true"
+          name="dest_fund" id="dest-fund"
+          [(ngModel)]="destFund" [asyncDataSource]="fundDataSource"
+          i18n-placeholder placeholder="Select fund..."></eg-combobox>
+        </div>
+      </div>
+      <div class="form-group row">
+        <label for="source_amount" class="col-sm-4 col-form-label" i18n>Source Amount</label>
+        <div class="col-sm-8">
+          <input class="form-control" type="number" name="source_amount" id="source_amount" [(ngModel)]="sourceAmount">
+        </div>
+      </div>
+      <div class="form-group row">
+        <div class="col-sm-4"></div>
+        <div class="col-sm-8" i18n>
+          <i>Amount to transfer from {{sourceFund?.name()}} ({{sourceFund?.code()}} ({{sourceFund?.year()}}) ({{sourceFund?.org().shortname()}}))</i>
+        </div>
+      </div>
+      <div class="form-group row">
+        <label for="note" class="col-sm-4 col-form-label" i18n>Note</label>
+        <div class="col-sm-8">
+          <input class="form-control" type="text" name="note" id="note" [(ngModel)]="note">
+        </div>
+      </div>
+    </form>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-info"
+      [disabled]="!destFund || (sourceAmount <= 0)"
+      (click)="transfer()" i18n>Transfer</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Close</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-transfer-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/funds/fund-transfer-dialog.component.ts
new file mode 100644 (file)
index 0000000..85ad875
--- /dev/null
@@ -0,0 +1,112 @@
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm} from '@angular/forms';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {Pager} from '@eg/share/util/pager';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PermService} from '@eg/core/perm.service';
+import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {Observable} from 'rxjs';
+import {map} from 'rxjs/operators';
+
+@Component({
+  selector: 'eg-fund-transfer-dialog',
+  templateUrl: './fund-transfer-dialog.component.html'
+})
+
+export class FundTransferDialogComponent
+  extends DialogComponent implements OnInit {
+
+    @Input() sourceFund: IdlObject;
+    doneLoading: boolean = false;
+
+    @ViewChild('successString', { static: true }) successString: StringComponent;
+    @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent;
+    @ViewChild('fundSelector', { static: false }) tagSelector: ComboboxComponent;
+
+    fundDataSource: (term: string) => Observable<ComboboxEntry>;
+    destFund: ComboboxEntry = null;
+    sourceAmount: number = 0.0;
+    note = null;
+    constructor(
+        private idl: IdlService,
+        private evt: EventService,
+        private net: NetService,
+        private auth: AuthService,
+        private pcrud: PcrudService,
+        private perm: PermService,
+        private toast: ToastService,
+        private modal: NgbModal
+    ) {
+        super(modal);
+    }
+    
+    ngOnInit() {
+        this.destFund = null;
+        this.onOpen$.subscribe(() => this._initRecord());
+        this.fundDataSource = term => {
+            const field = 'code';
+            const args = {};
+            const extra_args = { order_by : {} };
+            args[field] = {'ilike': `%${term}%`}; // could -or search on label
+            args['active'] = 't';
+            extra_args['order_by']['acqf'] = field;
+            extra_args['limit'] = 100;
+            extra_args['flesh'] = 1;
+            const flesh_fields: Object = {};
+            flesh_fields['acqf'] = ['org'];
+            extra_args['flesh_fields'] = flesh_fields;
+            return this.pcrud.search('acqf', args, extra_args).pipe(map(data => {
+                return {
+                    id: data.id(),
+                    label: data.code()
+                           + ' (' + data.year() + ')'
+                           + ' (' + data.org().shortname() + ')',
+                    fm: data
+                };
+            }));
+        };
+    }
+
+    private _initRecord() {
+        this.doneLoading = false;
+        this.destFund = null;
+        this.sourceAmount = 0;
+        this.note = null;
+        this.doneLoading = true;
+    }
+
+    transfer() {
+        this.net.request(
+            'open-ils.acq',
+            'open-ils.acq.funds.transfer_money',
+            this.auth.token(),
+            this.sourceFund.id(),
+            this.sourceAmount,
+            this.destFund.id,
+            null,
+            this.note
+        ).subscribe(
+            res => {
+                this.successString.current()
+                    .then(str => this.toast.success(str));
+                this.close(true);
+            },
+            res => {
+                this.updateFailedString.current()
+                    .then(str => this.toast.danger(str));
+                this.close(false);
+            }
+        )
+    }
+
+}
index 5f3892b..3c7d82b 100644 (file)
@@ -8,6 +8,7 @@ import {FundDetailsDialogComponent} from './fund-details-dialog.component';
 import {FundingSourcesComponent} from './funding-sources.component';
 import {FundingSourceTransactionsDialogComponent} from './funding-source-transactions-dialog.component';
 import {FundTagsComponent} from './fund-tags.component';
+import {FundTransferDialogComponent} from './fund-transfer-dialog.component';
 
 @NgModule({
   declarations: [
@@ -16,7 +17,8 @@ import {FundTagsComponent} from './fund-tags.component';
     FundDetailsDialogComponent,
     FundingSourcesComponent,
     FundingSourceTransactionsDialogComponent,
-    FundTagsComponent
+    FundTagsComponent,
+    FundTransferDialogComponent
   ],
   imports: [
     StaffCommonModule,