From: Bill Erickson Date: Mon, 8 Mar 2021 21:31:13 +0000 (-0500) Subject: LP1904036 Billing / payments X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=868fc72cf2e82415ddc049b5d62fe2bf647b6b7f;p=evergreen%2Fpines.git LP1904036 Billing / payments Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.css b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.css index ce9c2ae648..216b6d64db 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.css +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.css @@ -10,3 +10,7 @@ border-bottom: 1px solid rgba(0,0,0,.125); } +.num-receipts { + width: 4em; +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html index 8ab8da883b..1db92a88f3 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html @@ -109,7 +109,7 @@
+ (ngModelChange)="updatePendingColumn()" id="pay-amount" [min]="0"/>
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ # Receipts +
+ +
+
+
+
+ + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts index bea0bf25e4..74ba8ae3f1 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts @@ -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 { 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 { 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 { + + if (this.disableAutoPrint || !this.receiptOnPayment) { + return Promise.resolve(); + } + + // TODO + // return this.printer.pr + } + + compilePayments(): Array> { + 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 { - this.ccPaymentParams = {}; - + // Credit card info + getCcParams(): Promise { 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; + } }); } } diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts index dc451b2dcd..31b179fbeb 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts @@ -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, diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html index 05ac82d706..5c2eb179ea 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html @@ -89,7 +89,7 @@
Fines Owed
-
{{context.patronStats.fines.total_owed | currency}}
+
{{context.patronStats.fines.balance_owed | currency}}
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 index 0000000000..04dd4c0bb7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + + 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 index 0000000000..e4a05916cd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/billing/billing-dialog.component.ts @@ -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 { + + // 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 index 0000000000..8bfec28a0f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/billing/billing.module.ts @@ -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 index 0000000000..be129325ae --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/billing/billing.service.ts @@ -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 { + 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 { + 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>, + paymentNote?: string, + checkNumber?: string, + creditCardParams?: CreditCardPaymentParams, + convertChangeToCredit?: boolean): Promise { + + 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 index 0000000000..9176a4f6ee --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.html @@ -0,0 +1,115 @@ + + + + + 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 index 0000000000..54582779bf --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/billing/credit-card-dialog.component.ts @@ -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 index 04dd4c0bb7..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - 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 index fb27cc351e..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.ts +++ /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 { - - // 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/circ/circ.module.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts index 20fc9b965e..6dafdfd5f1 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts @@ -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 diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts index 931afac50f..f92007370d 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts @@ -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 { - 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 index 9176a4f6ee..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - 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 index 70965a7dac..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/credit-card-dialog.component.ts +++ /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() { - } -} - diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts index 180ff4576a..3954c2198b 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts @@ -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 diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts index 5845cb6d45..96357a6712 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts @@ -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, diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/mark-damaged-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings/mark-damaged-dialog.component.ts index 7bd0d17a2b..6d586d18cd 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holdings/mark-damaged-dialog.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/mark-damaged-dialog.component.ts @@ -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 { - 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()})); }); }