LP1904036 Billing / payments
authorBill Erickson <berickxx@gmail.com>
Mon, 8 Mar 2021 21:31:13 +0000 (16:31 -0500)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:25 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
20 files changed:
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.css
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html
Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/billing/billing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/billing/billing.service.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.html [deleted file]
Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.ts [deleted file]
Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts
Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts
Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html [deleted file]
Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts [deleted file]
Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts
Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts
Open-ILS/src/eg2/src/app/staff/share/holdings/mark-damaged-dialog.component.ts

index 8ab8da8..1db92a8 100644 (file)
     <div class="ml-2"><label for="pay-amount" i18n>Payment Received:</label></div>
     <div class="ml-1">
       <input type="number" class="form-control" [(ngModel)]="paymentAmount"
-        id="pay-amount" [min]="0"/>
+        (ngModelChange)="updatePendingColumn()" id="pay-amount" [min]="0"/>
     </div>
     <div class="ml-2 form-check form-check-inline">
       <input class="form-check-input" type="checkbox" 
 
 </eg-grid>
 
+<div class="row mt-2">
+  <div class="col-lg-12 d-flex">
+    <div class="flex-1"></div>
+    <div class="d-flex flex-colum justify-content-end">
+      <div class="form-check form-check-inline">
+      <input class="form-check-input" type="checkbox" id="patron-credit-cbox" 
+        [(ngModel)]="convertChangeToCredit"/>
+      <label class="form-check-label" for="patron-credit-cbox" i18n>
+        Convert Change To Patron Credit
+      </label>
+      </div>
+    </div>
+    <div class="d-flex flex-colum justify-content-end">
+      <div class="form-check form-check-inline ml-2">
+      <input class="form-check-input" type="checkbox" id="receipt-on-payment-cbox" 
+        [(ngModel)]="receiptOnPayment"/>
+      <label class="form-check-label" for="receipt-on-payment-cbox" i18n>
+        Receipt On Payment
+      </label>
+      </div>
+    </div>
+    <div class="form-inline ml-2">
+      <div class="input-group">
+        <div class="input-group-prepend">
+          <span class="input-group-text" i18n># Receipts</span>
+        </div>
+        <input type="number" class="form-control num-receipts" [(ngModel)]="numReceipts"/>
+      </div>
+    </div>
+  </div>
+</div>
+
+
+
index bea0bf2..74ba8ae 100644 (file)
@@ -18,8 +18,9 @@ import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
 import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
 import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
-import {CreditCardDialogComponent, CreditCardPaymentParams
-    } from '@eg/staff/share/circ/credit-card-dialog.component';
+import {CreditCardDialogComponent
+    } from '@eg/staff/share/billing/credit-card-dialog.component';
+import {BillingService, CreditCardPaymentParams} from '@eg/staff/share/billing/billing.service';
 
 interface BillGridEntry extends CircDisplayInfo {
     xact: IdlObject // mbt
@@ -61,11 +62,14 @@ export class BillsComponent implements OnInit, AfterViewInit {
     checkNumber: string;
     paymentAmount: number;
     annotatePayment = false;
-    annotation: string;
+    paymentNote: string;
     entries: BillGridEntry[];
     convertChangeToCredit = false;
     receiptOnPayment = false;
+    applyingPayment = false;
+    numReceipts = 1;
     ccPaymentParams: CreditCardPaymentParams;
+    disableAutoPrint = false;
 
     maxPayAmount = 100000;
     warnPayAmount = 1000;
@@ -88,6 +92,7 @@ export class BillsComponent implements OnInit, AfterViewInit {
         private auth: AuthService,
         private serverStore: ServerStoreService,
         private circ: CircService,
+        private billing: BillingService,
         public patronService: PatronService,
         public context: PatronContextService
     ) {}
@@ -118,14 +123,25 @@ export class BillsComponent implements OnInit, AfterViewInit {
     applySettings(): Promise<any> {
         return this.serverStore.getItemBatch([
             'ui.circ.billing.amount_warn',
-            'ui.circ.billing.amount_limit'
+            'ui.circ.billing.amount_limit',
+            'circ.staff_client.do_not_auto_attempt_print'
         ]).then(sets => {
             this.maxPayAmount = sets['ui.circ.billing.amount_limit'] || 100000;
             this.warnPayAmount = sets['ui.circ.billing.amount_warn'] || 1000;
+
+            const noPrint = sets['circ.staff_client.do_not_auto_attempt_print'];
+            if (noPrint && noPrint.includes('Bill Pay')) {
+                this.disableAutoPrint = true;
+            }
         });
     }
 
     ngAfterViewInit() {
+        // Recaclulate the amount owed per selected transaction as the
+        // grid rows selections change.
+        this.billGrid.context.rowSelector.selectionChange
+        .subscribe(_ => this.updatePendingColumn());
+
         this.focusPayAmount();
     }
 
@@ -136,29 +152,26 @@ export class BillsComponent implements OnInit, AfterViewInit {
         });
     }
 
-    load() {
+    load(): Promise<any> {
 
         this.summary = null;
         this.entries = [];
         this.gridDataSource.requestingData = true;
 
-        this.net.request('open-ils.actor',
+        return this.net.request('open-ils.actor',
             'open-ils.actor.user.transactions.for_billing',
             this.auth.token(), this.patronId
-        ).subscribe(
-            resp => {
-                if (!this.summary) { // 1st response is summary
-                    this.summary = resp;
-                } else {
-                    this.entries.push(this.formatForDisplay(resp));
-                }
-            },
-            null,
-            () => {
-                this.gridDataSource.requestingData = false;
-                this.billGrid.reload();
+        ).pipe(tap(resp => {
+            if (!this.summary) { // 1st response is summary
+                this.summary = resp;
+            } else {
+                this.entries.push(this.formatForDisplay(resp));
             }
-        );
+        })).toPromise()
+        .then(_ => {
+            this.gridDataSource.requestingData = false;
+            this.billGrid.reload();
+        });
     }
 
     formatForDisplay(xact: IdlObject): BillGridEntry {
@@ -217,11 +230,11 @@ export class BillsComponent implements OnInit, AfterViewInit {
     pendingPaymentInfo(): {payment: number, change: number} {
 
         const amt = this.paymentAmount || 0;
+        const owedSelected = this.owedSelected();
 
-        if (amt >= this.paidSelected()) {
-            const owedSelected = this.owedSelected();
+        if (amt >= owedSelected) {
             return {
-                payment : this.owedSelected(),
+                payment : owedSelected,
                 change : amt - owedSelected
             }
         }
@@ -233,6 +246,8 @@ export class BillsComponent implements OnInit, AfterViewInit {
         if (!this.billGrid) { return true; } // still loading
 
         return (
+            this.applyingPayment ||
+            !this.pendingPayment() ||
             this.paymentAmount === 0 ||
             (this.paymentAmount < 0 && this.paymentType !== 'refund') ||
             this.billGrid.context.rowSelector.selected().length === 0
@@ -274,12 +289,50 @@ export class BillsComponent implements OnInit, AfterViewInit {
 
         if (this.amountExceedsMax()) { return; }
 
-        this.annotation = '';
+        this.applyingPayment = true;
+        this.paymentNote = '';
+        this.ccPaymentParams = {};
 
         this.verifyPayAmount()
         .then(_ => this.annotate())
-        .then(_ => this.addCcArgs())
-        .catch(err => console.debug('Payment was canceled:', err));
+        .then(_ => this.getCcParams())
+        .then(_ => {
+            return this.billing.applyPayment(
+                this.patronId,
+                this.patron().last_xact_id(),
+                this.paymentType,
+                this.compilePayments(),
+                this.paymentNote,
+                this.checkNumber,
+                this.ccPaymentParams,
+                this.convertChangeToCredit
+            );
+        })
+        .then(paymentIds => this.handlePayReceipt(paymentIds))
+        .then(_ => this.load())
+        .then(_ => this.context.refreshPatron())
+        .catch(msg => console.debug('Payment Canceled:', msg))
+        .finally(() => this.applyingPayment = false);
+    }
+
+    handlePayReceipt(paymentIds: number[]): Promise<any> {
+
+        if (this.disableAutoPrint || !this.receiptOnPayment) {
+            return Promise.resolve();
+        }
+
+        // TODO
+        // return this.printer.pr
+    }
+
+    compilePayments(): Array<Array<number>> {
+        const payments = [];
+        this.entries.forEach(row => {
+            if (row.paymentPending) {
+                payments.push([row.xact.id(), row.paymentPending]);
+            }
+        });
+        return payments;
     }
 
     amountExceedsMax(): boolean {
@@ -288,9 +341,8 @@ export class BillsComponent implements OnInit, AfterViewInit {
         return true;
     }
 
-    addCcArgs(): Promise<any> {
-        this.ccPaymentParams = {};
-
+    // Credit card info
+    getCcParams(): Promise<any> {
         if (this.paymentType !== 'credit_card_payment') {
             return Promise.resolve();
         }
@@ -322,9 +374,47 @@ export class BillsComponent implements OnInit, AfterViewInit {
         return this.annotateDialog.open().toPromise()
         .then(value => {
             if (!value) {
+                // TODO: there is no way in PromptDialog to
+                // differentiate between canceling the dialog and
+                // submitting the dialog with no value.  In this case,
+                // if the dialog is submitted with no value, we may want
+                // to leave the dialog open so a value can be applied.
                 return Promise.reject('No annotation supplied');
             }
-            this.annotation = value;
+            this.paymentNote = value;
+        });
+    }
+
+    updatePendingColumn() {
+
+        // Reset...
+        this.entries.forEach(row => row.paymentPending = 0);
+
+        var amount = this.pendingPayment();
+        let done = false;
+
+        this.billGrid.context.rowSelector.selected().forEach(index => {
+            if (done) { return; }
+
+            const row = this.billGrid.context.getRowByIndex(index);
+            const owed = Number(row.xact.summary().balance_owed());
+
+            if (amount > owed) {
+                // Pending payment amount exceeds balance of this
+                // row.  Pay the entire amount
+                row.paymentPending = owed;
+                amount -= owed;
+
+            } else {
+                // balance owed on the current item matches or exceeds
+                // the pending payment.  Apply the full remainder of
+                // the payment to this item... and we're done.
+                //
+                // Limit to two decimal places to avoid floating point
+                // issues and cast back to number to match data type.
+                row.paymentPending = Number(amount.toFixed(2));
+                done = true;
+            }
         });
     }
 }
index dc451b2..31b179f 100644 (file)
@@ -4,6 +4,7 @@ import {PatronResolver} from './resolver.service';
 import {FmRecordEditorModule} from '@eg/share/fm-editor/fm-editor.module';
 import {StaffCommonModule} from '@eg/staff/common.module';
 import {HoldsModule} from '@eg/staff/share/holds/holds.module';
+import {BillingModule} from '@eg/staff/share/billing/billing.module';
 import {CircModule} from '@eg/staff/share/circ/circ.module';
 import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
 import {BookingModule} from '@eg/staff/share/booking/booking.module';
@@ -37,6 +38,7 @@ import {BillsComponent} from './bills.component';
   imports: [
     StaffCommonModule,
     FmRecordEditorModule,
+    BillingModule,
     CircModule,
     HoldsModule,
     HoldingsModule,
index 05ac82d..5c2eb17 100644 (file)
@@ -89,7 +89,7 @@
     <div class="row mb-1"
       [ngClass]="{'alert alert-danger p-0': context.patronStats.fines.total_owed > 0}">
       <div class="col-lg-5" i18n>Fines Owed</div>
-      <div class="col-lg-7">{{context.patronStats.fines.total_owed | currency}}</div>
+      <div class="col-lg-7">{{context.patronStats.fines.balance_owed | currency}}</div>
     </div>
 
     <!-- TODO GROUP FINES -->
diff --git a/Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.html
new file mode 100644 (file)
index 0000000..04dd4c0
--- /dev/null
@@ -0,0 +1,85 @@
+<eg-string #successMsg text="Successfully Added Billing" i18n-text></eg-string>
+<eg-string #errorMsg text="Failed To Add Billing" i18n-text></eg-string>
+
+<!-- putting this here guarantees it's available to ViewChild before open -->
+<ng-template #bTypes>
+  <eg-combobox #bTypeCbox [entries]="billingTypes" 
+    [required]="true" (onChange)="btChanged($event)"></eg-combobox>
+</ng-template>
+
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title" i18n>
+      Bill Patron: 
+        {{xact.usr().family_name()}}, 
+        {{xact.usr().first_given_name()}} : 
+        {{xact.usr().card().barcode()}}
+    </h4>
+    <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">
+
+    <div class="row">
+      <div class="col-lg-2" i18n>Bill #</div>
+      <div class="col-lg-4">{{xact.id()}}</div>
+      <div class="col-lg-4" i18n>Total Billed</div>
+      <div class="col-lg-2">{{xact.summary().total_owed() | currency}}</div>
+    </div>
+    <div class="row">
+      <div class="col-lg-2" i18n>Type</div>
+      <div class="col-lg-4">{{xact.summary().xact_type()}}</div>
+      <div class="col-lg-4" i18n>Total Paid</div>
+      <div class="col-lg-2">{{xact.summary().total_paid() | currency}}</div>
+    </div>
+    <div class="row">
+      <div class="col-lg-2" i18n>Start</div>
+      <div class="col-lg-4">{{xact.xact_start() | date:'short'}}</div>
+      <div class="col-lg-4" i18n>Balance Owed</div>
+      <div class="col-lg-2">{{xact.summary().balance_owed() | currency}}</div>
+    </div>
+    <div class="row">
+      <div class="col-lg-2" i18n>Finish</div>
+      <div class="col-lg-4">{{xact.xact_finish() | date:'short'}}</div>
+      <div class="col-lg-4" i18n>Renewal?</div>
+      <div class="col-lg-2"><eg-bool [value]="isRenewal()"></eg-bool></div>
+    </div>
+
+    <hr/>
+
+    <div class="form-validated">
+      <div class="row mt-2">
+        <div class="col-lg-4" i18n>Location</div>
+        <div class="col-lg-8" i18n>{{hereOrg}}</div>
+      </div>
+      <div class="row mt-2">
+        <div class="col-lg-4" i18n>Billing Type</div>
+        <div class="col-lg-8">
+          <ng-container *ngTemplateOutlet="bTypes"></ng-container>
+        </div>
+      </div>
+      <div class="row mt-2">
+        <div class="col-lg-4" i18n>Amount</div>
+        <div class="col-lg-8" i18n>
+          <input type="number" class="form-control" id="amount-input"
+            required [(ngModel)]="amount" [min]="0"/>
+        </div>
+      </div>
+      <div class="row mt-2">
+        <div class="col-lg-4" i18n>Note</div>
+        <div class="col-lg-8" i18n>
+          <textarea class="form-control" [rows]="3" [(ngModel)]="note"></textarea>
+        </div>
+      </div>
+    </div>
+
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" [disabled]="!saveable()"
+      (click)="submit()" i18n>Submit Bill</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.ts
new file mode 100644 (file)
index 0000000..e4a0591
--- /dev/null
@@ -0,0 +1,132 @@
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Observable} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {OrgService} from '@eg/core/org.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {BillingService} from './billing.service';
+
+/* Add a billing to a transaction */
+
+const DEFAULT_BILLING_TYPE = 101; // Stock "Misc"
+
+@Component({
+  selector: 'eg-add-billing-dialog',
+  templateUrl: 'billing-dialog.component.html'
+})
+
+export class AddBillingDialogComponent
+    extends DialogComponent implements OnInit {
+
+    @Input() xactId: number;
+
+    xact: IdlObject;
+    billingType: ComboboxEntry;
+    billingTypes: ComboboxEntry[] = [];
+    hereOrg: string;
+    amount: number;
+    note: string;
+
+    @ViewChild('successMsg') private successMsg: StringComponent;
+    @ViewChild('errorMsg') private errorMsg: StringComponent;
+    @ViewChild('bTypeCbox') private bTypeCbox: ComboboxComponent;
+
+    constructor(
+        private modal: NgbModal, // required for passing to parent
+        private toast: ToastService,
+        private net: NetService,
+        private idl: IdlService,
+        private evt: EventService,
+        private pcrud: PcrudService,
+        private billing: BillingService,
+        private org: OrgService,
+        private auth: AuthService) {
+        super(modal);
+    }
+
+    ngOnInit() {
+        this.billing.getUserBillingTypes().then(types => {
+            this.billingTypes = types.map(bt => {
+                return {id: bt.id(), label: bt.name(), fm: bt};
+            });
+        });
+
+        this.hereOrg = this.org.get(this.auth.user().ws_ou()).shortname();
+
+        this.onOpen$.subscribe(_ => {
+            this.amount = null;
+            this.note = '';
+            this.bTypeCbox.selectedId = DEFAULT_BILLING_TYPE;
+            const node = document.getElementById('amount-input');
+            if (node) { node.focus(); }
+        });
+    }
+
+    open(options: NgbModalOptions = {}): Observable<any> {
+
+        // Fetch the xact data before opening the dialog.
+        return this.pcrud.retrieve('mbt', this.xactId, {
+            flesh: 2,
+            flesh_fields: {
+                mbt: ['usr', 'summary', 'circulation'],
+                au: ['card']
+            }
+        }).pipe(switchMap(xact => {
+            this.xact = xact;
+            return super.open(options);
+        }));
+    }
+
+    isRenewal(): boolean {
+        return (
+            this.xact &&
+            this.xact.circulation() &&
+            this.xact.circulation().parent_circ() !== null
+        );
+    }
+
+    btChanged(entry: ComboboxEntry) {
+        this.billingType = entry;
+        if (entry && entry.fm.default_price()) {
+            this.amount = entry.fm.default_price();
+        }
+    }
+
+    saveable(): boolean {
+        return this.billingType && this.amount > 0;
+    }
+
+    submit() {
+        const bill = this.idl.create('mb');
+        bill.xact(this.xactId);
+        bill.amount(this.amount);
+        bill.btype(this.billingType.id);
+        bill.billing_type(this.billingType.label);
+        bill.note(this.note);
+
+        this.net.request(
+            'open-ils.circ',
+            'open-ils.circ.money.billing.create',
+            this.auth.token(), bill
+        ).subscribe(billId => {
+
+            const evt = this.evt.parse(billId);
+            if (evt) {
+                console.error(evt);
+                alert(evt);
+                this.close(null);
+            } else {
+                this.close(billId);
+            }
+        });
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/share/billing/billing.module.ts b/Open-ILS/src/eg2/src/app/staff/share/billing/billing.module.ts
new file mode 100644 (file)
index 0000000..8bfec28
--- /dev/null
@@ -0,0 +1,24 @@
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {BillingService} from './billing.service';
+import {AddBillingDialogComponent} from './billing-dialog.component';
+import {CreditCardDialogComponent} from './credit-card-dialog.component';
+
+@NgModule({
+    declarations: [
+        CreditCardDialogComponent,
+        AddBillingDialogComponent
+    ],
+    imports: [
+        StaffCommonModule
+    ],
+    exports: [
+        AddBillingDialogComponent,
+        CreditCardDialogComponent
+    ],
+    providers: [
+        BillingService
+    ]
+})
+
+export class BillingModule {}
diff --git a/Open-ILS/src/eg2/src/app/staff/share/billing/billing.service.ts b/Open-ILS/src/eg2/src/app/staff/share/billing/billing.service.ts
new file mode 100644 (file)
index 0000000..be12932
--- /dev/null
@@ -0,0 +1,105 @@
+import {Injectable} from '@angular/core';
+import {Observable, empty, from} from 'rxjs';
+import {map, concatMap, mergeMap} from 'rxjs/operators';
+import {IdlObject} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {EventService, EgEvent} from '@eg/core/event.service';
+import {AuthService} from '@eg/core/auth.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
+import {AudioService} from '@eg/share/util/audio.service';
+
+export interface CreditCardPaymentParams {
+    where_process?: 0 | 1,
+    approval_code?: string,
+    expire_month?: number,
+    expire_year?: number,
+    billing_first?: string,
+    billing_last?: string,
+    billing_address?: string,
+    billing_city?: string,
+    billing_state?: string,
+    billing_zip?: string,
+    note?: string
+}
+
+@Injectable()
+export class BillingService {
+    billingTypes: IdlObject[];
+    userBillingTypes: IdlObject[];
+
+    constructor(
+        private evt: EventService,
+        private org: OrgService,
+        private net: NetService,
+        private pcrud: PcrudService,
+        private auth: AuthService
+    ) {}
+
+    // Returns billing types owned "here", excluding system types
+    getUserBillingTypes(): Promise<IdlObject[]> {
+        if (this.userBillingTypes) {
+            return Promise.resolve(this.userBillingTypes);
+        }
+
+        return this.pcrud.search('cbt',
+            {   id: {'>': 100},
+                owner: this.org.fullPath(this.auth.user().ws_ou(), true)
+            },
+            {order_by: {cbt: 'name'}},
+            {atomic: true}
+        ).toPromise().then(types => this.userBillingTypes = types);
+    }
+
+    // Returns billing types owned "here", including system types
+    getBillingTypes(): Promise<IdlObject[]> {
+        if (this.billingTypes) {
+            return Promise.resolve(this.billingTypes);
+        }
+
+        return this.pcrud.search('cbt',
+            {owner: this.org.fullPath(this.auth.user().ws_ou(), true)},
+            {order_by: {cbt: 'name'}},
+            {atomic: true}
+        ).toPromise().then(types => this.billingTypes = types);
+    }
+
+    applyPayment(
+        patronId: number,
+        patronLastXactId: string,
+        paymentType: string,
+        payments: Array<Array<number>>,
+        paymentNote?: string,
+        checkNumber?: string,
+        creditCardParams?: CreditCardPaymentParams,
+        convertChangeToCredit?: boolean): Promise<number[]> {
+
+       return this.net.request(
+            'open-ils.circ',
+            'open-ils.circ.money.payment',
+            this.auth.token(), {
+                userid: patronId,
+                note: paymentNote || '',
+                payment_type: paymentType,
+                check_number: checkNumber,
+                payments: payments,
+                patron_credit: convertChangeToCredit,
+                cc_args: creditCardParams
+            }, patronLastXactId).toPromise()
+
+        .then(response => {
+
+            const evt = this.evt.parse(response);
+            if (evt) {
+                console.error(evt);
+                return Promise.reject(evt);
+            }
+
+            // TODO work log
+
+            return response.payments;
+        });
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.html
new file mode 100644 (file)
index 0000000..9176a4f
--- /dev/null
@@ -0,0 +1,115 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title" i18n>Credit Card Information</h4>
+    <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" *ngIf="args">
+
+    <div class="card">
+      <div class="card-header" i18n>Credit Card Info</div>
+      <div class="card-body form-validated">
+        <div class="row">
+          <div class="col-lg-4"><label i18n>Process Where</label></div>
+          <div class="col-lg-8">
+            <select class="form-control" [(ngModel)]="args.where_process">
+              <option [value]='1' [disabled]="!supportsExternal" i18n>
+                Process payment through Evergreen
+              </option>
+              <option [value]='0' i18n>
+                Record externally processed payment
+              </option>
+            </select>
+          </div>
+        </div>
+        <ng-container *ngIf="args.where_process == 1">
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Approval Code</label></div>
+            <div class="col-lg-8">
+              <input type="text" class="form-control" 
+                required [(ngModel)]="args.approval_code"/>
+            </div>
+          </div>
+        </ng-container>
+        <ng-container *ngIf="args.where_process == 0">
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Expire Month</label></div>
+            <div class="col-lg-8">
+              <input type="number" class="form-control" [min]="1"
+                required [(ngModel)]="args.expire_month"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Expire Year</label></div>
+            <div class="col-lg-8">
+              <input type="number" class="form-control" [min]="thisYear"
+                required [(ngModel)]="args.expire_year"/>
+            </div>
+          </div>
+        </ng-container>
+      </div>
+    </div>
+
+    <div class="card mt-2">
+      <div class="card-header" i18n>Optional Fields</div>
+      <div class="card-body form-validated">
+        <div class="row">
+          <div class="col-lg-4"><label i18n>Billing Name (first)</label></div>
+          <div class="col-lg-8">
+            <input type='text' class="form-control" [(ngModel)]="args.billing_first"/>
+          </div>
+        </div>
+        <div class="row mt-2">
+          <div class="col-lg-4"><label i18n>Billing Name (last)</label></div>
+          <div class="col-lg-8">
+            <input type='text' class="form-control" [(ngModel)]="args.billing_last"/>
+          </div>
+        </div>
+
+        <ng-container *ngIf="args.where_process == 0">
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>Address</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_address"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>City, town or village</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_city"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>State or province</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_state"/>
+            </div>
+          </div>
+          <div class="row mt-2">
+            <div class="col-lg-4"><label i18n>ZIP or postal code</label></div>
+            <div class="col-lg-8">
+              <input type='text' class="form-control" [(ngModel)]="args.billing_zip"/>
+            </div>
+          </div>
+        </ng-container>
+
+        <div class="row mt-2">
+          <div class="col-lg-4"><label i18n>Note</label></div>
+          <div class="col-lg-8">
+            <input type='text' class="form-control" [(ngModel)]="args.note"/>
+          </div>
+        </div>
+
+      </div>
+    </div>
+
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" [disabled]="!saveable()"
+      (click)="submit(args)" i18n>Submit</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.ts
new file mode 100644 (file)
index 0000000..5458277
--- /dev/null
@@ -0,0 +1,105 @@
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Observable} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {OrgService} from '@eg/core/org.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
+
+export interface CreditCardPaymentParams {
+    where_process?: 0 | 1,
+    approval_code?: string,
+    expire_month?: number,
+    expire_year?: number,
+    billing_first?: string,
+    billing_last?: string,
+    billing_address?: string,
+    billing_city?: string,
+    billing_state?: string,
+    billing_zip?: string,
+    note?: string
+}
+
+/* Dialog for collecting credit card payment information */
+
+@Component({
+  selector: 'eg-credit-card-dialog',
+  templateUrl: 'credit-card-dialog.component.html'
+})
+
+export class CreditCardDialogComponent
+    extends DialogComponent implements OnInit {
+
+    @Input() patron: IdlObject; // au, fleshed with billing address
+    args: CreditCardPaymentParams;
+    supportsExternal: boolean;
+    thisYear = new Date().getFullYear();
+
+    constructor(
+        private modal: NgbModal,
+        private toast: ToastService,
+        private net: NetService,
+        private idl: IdlService,
+        private evt: EventService,
+        private pcrud: PcrudService,
+        private org: OrgService,
+        private serverStore: ServerStoreService,
+        private auth: AuthService) {
+        super(modal);
+    }
+
+    ngOnInit() {
+
+        this.onOpen$.subscribe(_ => {
+
+            this.args = {
+                billing_first: this.patron.first_given_name(),
+                billing_last: this.patron.family_name(),
+            };
+
+            const addr =
+                this.patron.billing_address() || this.patron.mailing_address();
+
+            if (addr) {
+                this.args.billing_address = addr.street1() +
+                    (addr.street2() ? ' ' + addr.street2() : '');
+                this.args.billing_city = addr.city();
+                this.args.billing_state = addr.state();
+                this.args.billing_zip = addr.post_code();
+            }
+
+            this.supportsExternal = false;
+
+            this.serverStore.getItem('credit.processor.default')
+            .then(processor => {
+                if (processor && processor !== 'Stripe') {
+                    this.supportsExternal = true;
+                    this.args.where_process = 1;
+                }
+            })
+        });
+    }
+
+    saveable(): boolean {
+        if (!this.args) { return false; }
+
+        if (this.args.where_process === 0) {
+            return Boolean(this.args.approval_code);
+        }
+
+        return Boolean(this.args.expire_month) && Boolean(this.args.expire_year);
+    }
+
+
+    submit() {
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.html
deleted file mode 100644 (file)
index 04dd4c0..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-<eg-string #successMsg text="Successfully Added Billing" i18n-text></eg-string>
-<eg-string #errorMsg text="Failed To Add Billing" i18n-text></eg-string>
-
-<!-- putting this here guarantees it's available to ViewChild before open -->
-<ng-template #bTypes>
-  <eg-combobox #bTypeCbox [entries]="billingTypes" 
-    [required]="true" (onChange)="btChanged($event)"></eg-combobox>
-</ng-template>
-
-<ng-template #dialogContent>
-  <div class="modal-header bg-info">
-    <h4 class="modal-title" i18n>
-      Bill Patron: 
-        {{xact.usr().family_name()}}, 
-        {{xact.usr().first_given_name()}} : 
-        {{xact.usr().card().barcode()}}
-    </h4>
-    <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">
-
-    <div class="row">
-      <div class="col-lg-2" i18n>Bill #</div>
-      <div class="col-lg-4">{{xact.id()}}</div>
-      <div class="col-lg-4" i18n>Total Billed</div>
-      <div class="col-lg-2">{{xact.summary().total_owed() | currency}}</div>
-    </div>
-    <div class="row">
-      <div class="col-lg-2" i18n>Type</div>
-      <div class="col-lg-4">{{xact.summary().xact_type()}}</div>
-      <div class="col-lg-4" i18n>Total Paid</div>
-      <div class="col-lg-2">{{xact.summary().total_paid() | currency}}</div>
-    </div>
-    <div class="row">
-      <div class="col-lg-2" i18n>Start</div>
-      <div class="col-lg-4">{{xact.xact_start() | date:'short'}}</div>
-      <div class="col-lg-4" i18n>Balance Owed</div>
-      <div class="col-lg-2">{{xact.summary().balance_owed() | currency}}</div>
-    </div>
-    <div class="row">
-      <div class="col-lg-2" i18n>Finish</div>
-      <div class="col-lg-4">{{xact.xact_finish() | date:'short'}}</div>
-      <div class="col-lg-4" i18n>Renewal?</div>
-      <div class="col-lg-2"><eg-bool [value]="isRenewal()"></eg-bool></div>
-    </div>
-
-    <hr/>
-
-    <div class="form-validated">
-      <div class="row mt-2">
-        <div class="col-lg-4" i18n>Location</div>
-        <div class="col-lg-8" i18n>{{hereOrg}}</div>
-      </div>
-      <div class="row mt-2">
-        <div class="col-lg-4" i18n>Billing Type</div>
-        <div class="col-lg-8">
-          <ng-container *ngTemplateOutlet="bTypes"></ng-container>
-        </div>
-      </div>
-      <div class="row mt-2">
-        <div class="col-lg-4" i18n>Amount</div>
-        <div class="col-lg-8" i18n>
-          <input type="number" class="form-control" id="amount-input"
-            required [(ngModel)]="amount" [min]="0"/>
-        </div>
-      </div>
-      <div class="row mt-2">
-        <div class="col-lg-4" i18n>Note</div>
-        <div class="col-lg-8" i18n>
-          <textarea class="form-control" [rows]="3" [(ngModel)]="note"></textarea>
-        </div>
-      </div>
-    </div>
-
-  </div>
-  <div class="modal-footer">
-    <button type="button" class="btn btn-success" [disabled]="!saveable()"
-      (click)="submit()" i18n>Submit Bill</button>
-    <button type="button" class="btn btn-warning"
-      (click)="close()" i18n>Cancel</button>
-  </div>
-</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.ts
deleted file mode 100644 (file)
index fb27cc3..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-import {Component, OnInit, Input, ViewChild} from '@angular/core';
-import {Observable} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
-import {IdlObject, IdlService} from '@eg/core/idl.service';
-import {NetService} from '@eg/core/net.service';
-import {EventService} from '@eg/core/event.service';
-import {ToastService} from '@eg/share/toast/toast.service';
-import {PcrudService} from '@eg/core/pcrud.service';
-import {AuthService} from '@eg/core/auth.service';
-import {OrgService} from '@eg/core/org.service';
-import {DialogComponent} from '@eg/share/dialog/dialog.component';
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
-import {StringComponent} from '@eg/share/string/string.component';
-import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
-import {CircService} from './circ.service';
-
-/* Add a billing to a transaction */
-
-const DEFAULT_BILLING_TYPE = 101; // Stock "Misc"
-
-@Component({
-  selector: 'eg-add-billing-dialog',
-  templateUrl: 'billing-dialog.component.html'
-})
-
-export class AddBillingDialogComponent
-    extends DialogComponent implements OnInit {
-
-    @Input() xactId: number;
-
-    xact: IdlObject;
-    billingType: ComboboxEntry;
-    billingTypes: ComboboxEntry[] = [];
-    hereOrg: string;
-    amount: number;
-    note: string;
-
-    @ViewChild('successMsg') private successMsg: StringComponent;
-    @ViewChild('errorMsg') private errorMsg: StringComponent;
-    @ViewChild('bTypeCbox') private bTypeCbox: ComboboxComponent;
-
-    constructor(
-        private modal: NgbModal, // required for passing to parent
-        private toast: ToastService,
-        private net: NetService,
-        private idl: IdlService,
-        private evt: EventService,
-        private pcrud: PcrudService,
-        private circ: CircService,
-        private org: OrgService,
-        private auth: AuthService) {
-        super(modal);
-    }
-
-    ngOnInit() {
-        this.circ.getBillingTypes().then(types => {
-            this.billingTypes = types.map(bt => {
-                return {id: bt.id(), label: bt.name(), fm: bt};
-            });
-        });
-
-        this.hereOrg = this.org.get(this.auth.user().ws_ou()).shortname();
-
-        this.onOpen$.subscribe(_ => {
-            this.amount = null;
-            this.note = '';
-            this.bTypeCbox.selectedId = DEFAULT_BILLING_TYPE;
-            const node = document.getElementById('amount-input');
-            if (node) { node.focus(); }
-        });
-    }
-
-    open(options: NgbModalOptions = {}): Observable<any> {
-
-        // Fetch the xact data before opening the dialog.
-        return this.pcrud.retrieve('mbt', this.xactId, {
-            flesh: 2,
-            flesh_fields: {
-                mbt: ['usr', 'summary', 'circulation'],
-                au: ['card']
-            }
-        }).pipe(switchMap(xact => {
-            this.xact = xact;
-            return super.open(options);
-        }));
-    }
-
-    isRenewal(): boolean {
-        return (
-            this.xact &&
-            this.xact.circulation() &&
-            this.xact.circulation().parent_circ() !== null
-        );
-    }
-
-    btChanged(entry: ComboboxEntry) {
-        this.billingType = entry;
-        if (entry && entry.fm.default_price()) {
-            this.amount = entry.fm.default_price();
-        }
-    }
-
-    saveable(): boolean {
-        return this.billingType && this.amount > 0;
-    }
-
-    submit() {
-        const bill = this.idl.create('mb');
-        bill.xact(this.xactId);
-        bill.amount(this.amount);
-        bill.btype(this.billingType.id);
-        bill.billing_type(this.billingType.label);
-        bill.note(this.note);
-
-        this.net.request(
-            'open-ils.circ',
-            'open-ils.circ.money.billing.create',
-            this.auth.token(), bill
-        ).subscribe(billId => {
-
-            const evt = this.evt.parse(billId);
-            if (evt) {
-                console.error(evt);
-                alert(evt);
-                this.close(null);
-            } else {
-                this.close(billId);
-            }
-        });
-    }
-}
-
index 20fc9b9..6dafdfd 100644 (file)
@@ -1,6 +1,7 @@
 import {NgModule} from '@angular/core';
 import {StaffCommonModule} from '@eg/staff/common.module';
 import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
+import {BillingModule} from '@eg/staff/share/billing/billing.module';
 import {CircService} from './circ.service';
 import {CircGridComponent} from './grid.component';
 import {DueDateDialogComponent} from './due-date-dialog.component';
@@ -8,8 +9,6 @@ import {PrecatCheckoutDialogComponent} from './precat-dialog.component';
 import {ClaimsReturnedDialogComponent} from './claims-returned-dialog.component';
 import {CircComponentsComponent} from './components.component';
 import {CircEventsComponent} from './events-dialog.component';
-import {AddBillingDialogComponent} from './billing-dialog.component';
-import {CreditCardDialogComponent} from './credit-card-dialog.component';
 
 @NgModule({
     declarations: [
@@ -18,19 +17,16 @@ import {CreditCardDialogComponent} from './credit-card-dialog.component';
         DueDateDialogComponent,
         PrecatCheckoutDialogComponent,
         ClaimsReturnedDialogComponent,
-        CircEventsComponent,
-        CreditCardDialogComponent,
-        AddBillingDialogComponent
+        CircEventsComponent
     ],
     imports: [
         StaffCommonModule,
-        HoldingsModule
+        HoldingsModule,
+        BillingModule
     ],
     exports: [
         CircGridComponent,
-        CircComponentsComponent,
-        AddBillingDialogComponent,
-        CreditCardDialogComponent
+        CircComponentsComponent
     ],
     providers: [
         CircService
index 931afac..f920073 100644 (file)
@@ -166,7 +166,6 @@ export class CircService {
 
     components: CircComponentsComponent;
     nonCatTypes: IdlObject[] = null;
-    billingTypes: IdlObject[] = null;
     autoOverrideCheckoutEvents: {[textcode: string]: boolean} = {};
     suppressCheckinPopups = false;
     ignoreCheckinPrecats = false;
@@ -227,21 +226,6 @@ export class CircService {
         ).toPromise().then(types => this.nonCatTypes = types);
     }
 
-    getBillingTypes(): Promise<IdlObject[]> {
-        if (this.billingTypes) {
-            return Promise.resolve(this.billingTypes);
-        }
-
-        return this.pcrud.search('cbt',
-            {
-                id: {'>': 100}, // first 100 are reserved
-                owner: this.org.fullPath(this.auth.user().ws_ou(), true)
-            },
-            {order_by: {cbt: 'name'}},
-            {atomic: true}
-        ).toPromise().then(types => this.billingTypes = types);
-    }
-
     // Remove internal tracking variables on Param objects so they are
     // not sent to the server, which can result in autoload errors.
     apiParams(
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html
deleted file mode 100644 (file)
index 9176a4f..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-<ng-template #dialogContent>
-  <div class="modal-header bg-info">
-    <h4 class="modal-title" i18n>Credit Card Information</h4>
-    <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" *ngIf="args">
-
-    <div class="card">
-      <div class="card-header" i18n>Credit Card Info</div>
-      <div class="card-body form-validated">
-        <div class="row">
-          <div class="col-lg-4"><label i18n>Process Where</label></div>
-          <div class="col-lg-8">
-            <select class="form-control" [(ngModel)]="args.where_process">
-              <option [value]='1' [disabled]="!supportsExternal" i18n>
-                Process payment through Evergreen
-              </option>
-              <option [value]='0' i18n>
-                Record externally processed payment
-              </option>
-            </select>
-          </div>
-        </div>
-        <ng-container *ngIf="args.where_process == 1">
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>Approval Code</label></div>
-            <div class="col-lg-8">
-              <input type="text" class="form-control" 
-                required [(ngModel)]="args.approval_code"/>
-            </div>
-          </div>
-        </ng-container>
-        <ng-container *ngIf="args.where_process == 0">
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>Expire Month</label></div>
-            <div class="col-lg-8">
-              <input type="number" class="form-control" [min]="1"
-                required [(ngModel)]="args.expire_month"/>
-            </div>
-          </div>
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>Expire Year</label></div>
-            <div class="col-lg-8">
-              <input type="number" class="form-control" [min]="thisYear"
-                required [(ngModel)]="args.expire_year"/>
-            </div>
-          </div>
-        </ng-container>
-      </div>
-    </div>
-
-    <div class="card mt-2">
-      <div class="card-header" i18n>Optional Fields</div>
-      <div class="card-body form-validated">
-        <div class="row">
-          <div class="col-lg-4"><label i18n>Billing Name (first)</label></div>
-          <div class="col-lg-8">
-            <input type='text' class="form-control" [(ngModel)]="args.billing_first"/>
-          </div>
-        </div>
-        <div class="row mt-2">
-          <div class="col-lg-4"><label i18n>Billing Name (last)</label></div>
-          <div class="col-lg-8">
-            <input type='text' class="form-control" [(ngModel)]="args.billing_last"/>
-          </div>
-        </div>
-
-        <ng-container *ngIf="args.where_process == 0">
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>Address</label></div>
-            <div class="col-lg-8">
-              <input type='text' class="form-control" [(ngModel)]="args.billing_address"/>
-            </div>
-          </div>
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>City, town or village</label></div>
-            <div class="col-lg-8">
-              <input type='text' class="form-control" [(ngModel)]="args.billing_city"/>
-            </div>
-          </div>
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>State or province</label></div>
-            <div class="col-lg-8">
-              <input type='text' class="form-control" [(ngModel)]="args.billing_state"/>
-            </div>
-          </div>
-          <div class="row mt-2">
-            <div class="col-lg-4"><label i18n>ZIP or postal code</label></div>
-            <div class="col-lg-8">
-              <input type='text' class="form-control" [(ngModel)]="args.billing_zip"/>
-            </div>
-          </div>
-        </ng-container>
-
-        <div class="row mt-2">
-          <div class="col-lg-4"><label i18n>Note</label></div>
-          <div class="col-lg-8">
-            <input type='text' class="form-control" [(ngModel)]="args.note"/>
-          </div>
-        </div>
-
-      </div>
-    </div>
-
-  </div>
-  <div class="modal-footer">
-    <button type="button" class="btn btn-success" [disabled]="!saveable()"
-      (click)="submit(args)" i18n>Submit</button>
-    <button type="button" class="btn btn-warning"
-      (click)="close()" i18n>Cancel</button>
-  </div>
-</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts
deleted file mode 100644 (file)
index 70965a7..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-import {Component, OnInit, Input, ViewChild} from '@angular/core';
-import {Observable} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
-import {IdlObject, IdlService} from '@eg/core/idl.service';
-import {NetService} from '@eg/core/net.service';
-import {EventService} from '@eg/core/event.service';
-import {ToastService} from '@eg/share/toast/toast.service';
-import {PcrudService} from '@eg/core/pcrud.service';
-import {AuthService} from '@eg/core/auth.service';
-import {OrgService} from '@eg/core/org.service';
-import {ServerStoreService} from '@eg/core/server-store.service';
-import {DialogComponent} from '@eg/share/dialog/dialog.component';
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
-import {StringComponent} from '@eg/share/string/string.component';
-import {ComboboxEntry, ComboboxComponent} from '@eg/share/combobox/combobox.component';
-import {CircService} from './circ.service';
-
-export interface CreditCardPaymentParams {
-    where_process?: 0 | 1,
-    approval_code?: string,
-    expire_month?: number,
-    expire_year?: number,
-    billing_first?: string,
-    billing_last?: string,
-    billing_address?: string,
-    billing_city?: string,
-    billing_state?: string,
-    billing_zip?: string,
-    note?: string
-}
-
-/* Dialog for collecting credit card payment information */
-
-@Component({
-  selector: 'eg-credit-card-dialog',
-  templateUrl: 'credit-card-dialog.component.html'
-})
-
-export class CreditCardDialogComponent
-    extends DialogComponent implements OnInit {
-
-    @Input() patron: IdlObject; // au, fleshed with billing address
-    args: CreditCardPaymentParams;
-    supportsExternal: boolean;
-    thisYear = new Date().getFullYear();
-
-    constructor(
-        private modal: NgbModal,
-        private toast: ToastService,
-        private net: NetService,
-        private idl: IdlService,
-        private evt: EventService,
-        private pcrud: PcrudService,
-        private circ: CircService,
-        private org: OrgService,
-        private serverStore: ServerStoreService,
-        private auth: AuthService) {
-        super(modal);
-    }
-
-    ngOnInit() {
-
-        this.onOpen$.subscribe(_ => {
-
-            this.args = {
-                billing_first: this.patron.first_given_name(),
-                billing_last: this.patron.family_name(),
-            };
-
-            const addr =
-                this.patron.billing_address() || this.patron.mailing_address();
-
-            if (addr) {
-                this.args.billing_address = addr.street1() +
-                    (addr.street2() ? ' ' + addr.street2() : '');
-                this.args.billing_city = addr.city();
-                this.args.billing_state = addr.state();
-                this.args.billing_zip = addr.post_code();
-            }
-
-            this.supportsExternal = false;
-
-            this.serverStore.getItem('credit.processor.default')
-            .then(processor => {
-                if (processor && processor !== 'Stripe') {
-                    this.supportsExternal = true;
-                    this.args.where_process = 1;
-                }
-            })
-        });
-    }
-
-    saveable(): boolean {
-        if (!this.args) { return false; }
-
-        if (this.args.where_process === 0) {
-            return Boolean(this.args.approval_code);
-        }
-
-        return Boolean(this.args.expire_month) && Boolean(this.args.expire_year);
-    }
-
-
-    submit() {
-    }
-}
-
index 180ff45..3954c21 100644 (file)
@@ -31,7 +31,7 @@ import {MarkMissingDialogComponent
     } from '@eg/staff/share/holdings/mark-missing-dialog.component';
 import {ClaimsReturnedDialogComponent} from './claims-returned-dialog.component';
 import {ToastService} from '@eg/share/toast/toast.service';
-import {AddBillingDialogComponent} from './billing-dialog.component';
+import {AddBillingDialogComponent} from '@eg/staff/share/billing/billing-dialog.component';
 
 export interface CircGridEntry extends CircDisplayInfo {
     index: string; // class + id -- row index
index 5845cb6..96357a6 100644 (file)
@@ -1,5 +1,6 @@
 import {NgModule} from '@angular/core';
 import {StaffCommonModule} from '@eg/staff/common.module';
+import {BillingModule} from '@eg/staff/share/billing/billing.module';
 import {HoldingsService} from './holdings.service';
 import {MarkDamagedDialogComponent} from './mark-damaged-dialog.component';
 import {MarkMissingDialogComponent} from './mark-missing-dialog.component';
@@ -30,7 +31,8 @@ import {BatchItemAttrComponent} from './batch-item-attr.component';
       BatchItemAttrComponent
     ],
     imports: [
-        StaffCommonModule
+        StaffCommonModule,
+        BillingModule
     ],
     exports: [
       MarkDamagedDialogComponent,
index 7bd0d17..6d586d1 100644 (file)
@@ -13,6 +13,7 @@ import {DialogComponent} from '@eg/share/dialog/dialog.component';
 import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
 import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {BillingService} from '@eg/staff/share/billing/billing.service';
 
 /**
  * Dialog for marking items damaged and asessing related bills.
@@ -55,10 +56,10 @@ export class MarkDamagedDialogComponent
         private evt: EventService,
         private pcrud: PcrudService,
         private org: OrgService,
+        private billing: BillingService,
         private bib: BibRecordService,
         private auth: AuthService) {
         super(modal); // required for subclassing
-        this.billingTypes = [];
     }
 
     /**
@@ -84,16 +85,9 @@ export class MarkDamagedDialogComponent
 
     // Fetch-cache billing types
     getBillingTypes(): Promise<any> {
-        if (this.billingTypes.length > 1) {
-            return Promise.resolve();
-        }
-        return this.pcrud.search('cbt',
-            {owner: this.org.fullPath(this.auth.user().ws_ou(), true)},
-            {}, {atomic: true}
-        ).toPromise().then(bts => {
-            this.billingTypes = bts
-                .sort((a, b) => a.name() < b.name() ? -1 : 1)
-                .map(bt => ({id: bt.id(), label: bt.name()}));
+        return this.billing.getUserBillingTypes().then(types => {
+            this.billingTypes =
+                types.map(bt => ({id: bt.id(), label: bt.name()}));
         });
     }