<eg-credit-card-dialog [patron]="patron()" #creditCardDialog>
</eg-credit-card-dialog>
+<eg-add-billing-dialog [patronId]="patronId" [newXact]="true" #billingDialog>
+</eg-add-billing-dialog>
+
<!-- SUMMARY -->
<ng-container *ngIf="summary">
<div class="col-lg-3 pr-0 mr-0 border-right">
<div class="d-flex pt-1 pb-1 striped">
<div class="flex-4" i18n>Total Owed:</div>
- <div class="flex-1"
+ <div class="flex-1"
[ngClass]="{'font-weight-bold' : summary.balance_owed() > 0}">
{{summary.balance_owed() | currency}}</div>
</div>
(ngModelChange)="updatePendingColumn()" id="pay-amount" [min]="0"/>
</div>
<div class="ml-2 form-check form-check-inline">
- <input class="form-check-input" type="checkbox"
+ <input class="form-check-input" type="checkbox"
+ (ngModelChange)="applySetting('eg.circ.bills.annotatepayment', $event)"
id="annotate" [(ngModel)]="annotatePayment"/>
<label class="form-check-label" for="annotate" i18n>Annotate</label>
</div>
<div class="ml-2">
- <button class="btn btn-outline-dark" (click)="applyPayment()"
+ <button class="btn btn-outline-dark" (click)="applyPayment()"
[disabled]="disablePayment()" i18n>Apply Payment</button>
</div>
</div>
<ng-template #callNumberTemplate let-r="row">
<ng-container *ngIf="r.volume">
- {{r.volume.prefix().label()}} {{r.volume.label()}} {{r.volume.suffix().label()}}
+ {{r.volume.prefix().label()}} {{r.volume.label()}} {{r.volume.suffix().label()}}
</ng-container>
<ng-container *ngIf="!r.volume" i18n>UNCATALOGED</ng-container>
</ng-template>
-<eg-grid #billGrid [dataSource]="gridDataSource"
+<eg-grid #billGrid [dataSource]="gridDataSource"
[sortable]="true" [useLocalSort]="true"
[cellTextGenerator]="cellTextGenerator">
- <eg-grid-toolbar-action
- i18n-label label="Print Bills" (onClick)="printBills($event)">
- </eg-grid-toolbar-action>
+ <eg-grid-toolbar-button i18n-label label="Add Billing"
+ (onClick)="addBilling()"></eg-grid-toolbar-button>
+
+ <eg-grid-toolbar-button i18n-label label="Select All Refunds"
+ (onClick)="selectRefunds()"></eg-grid-toolbar-button>
+
+ <eg-grid-toolbar-action
+ i18n-label label="Print Bills" (onClick)="printBills($event)">
+ </eg-grid-toolbar-action>
<eg-grid-column path="xact.id" [index]="true" label="Bill #" i18n-label>
</eg-grid-column>
<eg-grid-column path="billingLocation"
label="Billing Location" i18n-label></eg-grid-column>
-
+
<eg-grid-column path="call_number" label="Call Number" i18n-label
[cellTemplate]="callNumberTemplate"></eg-grid-column>
<eg-grid-column name="copy_barcode" label="Item Barcode" i18n-label
[cellTemplate]="barcodeTemplate"></eg-grid-column>
- <eg-grid-column path="copy.location.name" label="Shelving Location"
+ <eg-grid-column path="copy.location.name" label="Shelving Location"
i18n-label></eg-grid-column>
- <eg-grid-column name="title" label="Title" i18n-label
+ <eg-grid-column name="title" label="Title" i18n-label
[cellTemplate]="titleTemplate"></eg-grid-column>
- <eg-grid-column path="xact.summary.balance_owed" datatype="money"
+ <eg-grid-column path="xact.summary.balance_owed" datatype="money"
label="Balance Owed" i18n-label></eg-grid-column>
- <eg-grid-column path="xact.summary.total_owed" datatype="money"
+ <eg-grid-column path="xact.summary.total_owed" datatype="money"
label="Total Billed" i18n-label></eg-grid-column>
- <eg-grid-column path="xact.summary.total_paid" datatype="money"
+ <eg-grid-column path="xact.summary.total_paid" datatype="money"
label="Total Paid" i18n-label></eg-grid-column>
<eg-grid-column name="paymentPending" datatype="money"
label="Payment Pending" i18n-label></eg-grid-column>
- <eg-grid-column name="author" [hidden]="true"
+ <eg-grid-column name="author" [hidden]="true"
label="Author" i18n-label></eg-grid-column>
<eg-grid-column path="usr" [hidden]="true"></eg-grid-column>
<eg-grid-column path="unrecovered" [hidden]="true"></eg-grid-column>
<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"
+ <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
</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"
+ <input class="form-check-input" type="checkbox" id="receipt-on-payment-cbox"
+ (ngModelChange)="applySetting('circ.bills.receiptonpay', $event)"
[(ngModel)]="receiptOnPayment"/>
<label class="form-check-label" for="receipt-on-payment-cbox" i18n>
Receipt On Payment
import {CreditCardDialogComponent
} from '@eg/staff/share/billing/credit-card-dialog.component';
import {BillingService, CreditCardPaymentParams} from '@eg/staff/share/billing/billing.service';
+import {AddBillingDialogComponent} from '@eg/staff/share/billing/billing-dialog.component';
interface BillGridEntry extends CircDisplayInfo {
xact: IdlObject // mbt
@ViewChild('maxPayDialog') private maxPayDialog: AlertDialogComponent;
@ViewChild('warnPayDialog') private warnPayDialog: ConfirmDialogComponent;
@ViewChild('creditCardDialog') private creditCardDialog: CreditCardDialogComponent;
+ @ViewChild('billingDialog') private billingDialog: AddBillingDialogComponent;
constructor(
private router: Router,
return from(page);
};
- this.applySettings().then(_ => this.load());
+ this.loadSettings().then(_ => this.load());
}
- applySettings(): Promise<any> {
+ loadSettings(): Promise<any> {
return this.serverStore.getItemBatch([
'ui.circ.billing.amount_warn',
'ui.circ.billing.amount_limit',
- 'circ.staff_client.do_not_auto_attempt_print'
+ 'circ.staff_client.do_not_auto_attempt_print',
+ 'circ.bills.receiptonpay',
+ 'eg.circ.bills.annotatepayment'
+
]).then(sets => {
this.maxPayAmount = sets['ui.circ.billing.amount_limit'] || 100000;
this.warnPayAmount = sets['ui.circ.billing.amount_warn'] || 1000;
+ this.receiptOnPayment = sets['circ.bills.receiptonpay'];
+ this.annotatePayment = sets['eg.circ.bills.annotatepayment'];
const noPrint = sets['circ.staff_client.do_not_auto_attempt_print'];
if (noPrint && noPrint.includes('Bill Pay')) {
});
}
+ applySetting(name: string, value: any) {
+ this.serverStore.setItem(name, value);
+ }
+
ngAfterViewInit() {
// Recaclulate the amount owed per selected transaction as the
// grid rows selections change.
// summary, and slot them back into the entries array.
load(refreshXacts?: number[]): Promise<any> {
- let entries = [];
+ if (!refreshXacts) { this.entries = []; }
this.summary = null;
this.gridDataSource.requestingData = true;
}
if (refreshXacts) {
-
- // Slot the updated xact back into place
- entries.push(this.formatForDisplay(resp));
- entries = entries.map(e => {
- if (e.xact.id() === resp.id()) {
- return this.formatForDisplay(resp);
- }
- return e;
- });
+ let idx = 0;
+ for (;idx < this.entries.length; idx++) {
+ const entry = this.entries[idx];
+ if (entry.xact.id() === resp.id()) { break; }
+ }
+
+ if (idx < this.entries.length) {
+ // Update the existing entry
+ this.entries[idx] = this.formatForDisplay(resp);
+ } else {
+ // Adding a new transaction (e.g. from new billing)
+ this.entries.push(this.formatForDisplay(resp));
+ }
} else {
- entries.push(this.formatForDisplay(resp));
+ this.entries.push(this.formatForDisplay(resp));
}
})).toPromise()
.then(_ => {
this.gridDataSource.requestingData = false;
- this.entries = entries;
this.billGrid.reload();
});
}
}
applyPayment() {
-
if (this.amountExceedsMax()) { return; }
this.applyingPayment = true;
});
}
+ selectRefunds() {
+ this.billGrid.context.rowSelector.clear();
+ this.entries.forEach(entry => {
+ if (entry.xact.summary().balance_owed() < 0) {
+ this.billGrid.context.toggleSelectOneRow(entry.xact.id());
+ }
+ });
+ }
+ addBilling() {
+ this.billingDialog.open().subscribe(data => {
+ if (data) {
+ this.context.refreshPatron();
+ this.load([data.xactId]);
+ }
+ });
+ }
}
// Some of these are used by the shared circ services.
// Precache them since we're making the call anyway.
return this.store.getItemBatch([
+ 'circ.bills.receiptonpay',
+ 'eg.circ.bills.annotatepayment',
'eg.circ.patron.summary.collapse',
'circ.do_not_tally_claims_returned',
'circ.tally_lost',
<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()}}
+ {{patron.family_name()}},
+ {{patron.first_given_name()}} :
+ {{patron.card().barcode()}}
</h4>
<button type="button" class="close"
i18n-aria-label aria-label="Close" (click)="close()">
</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>
+ <ng-container *ngIf="xact">
+ <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>
+ </ng-container>
<hr/>
import {Component, OnInit, Input, ViewChild} from '@angular/core';
-import {Observable} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
+import {Observable, empty} from 'rxjs';
+import {switchMap, tap} 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';
extends DialogComponent implements OnInit {
@Input() xactId: number;
+ @Input() newXact = false;
+ @Input() patronId: number;
+ patron: IdlObject;
xact: IdlObject;
billingType: ComboboxEntry;
billingTypes: ComboboxEntry[] = [];
this.billingTypes = types.map(bt => {
return {id: bt.id(), label: bt.name(), fm: bt};
});
+ this.billingType = this.billingTypes
+ .filter(t => t.id === DEFAULT_BILLING_TYPE)[0];
});
this.hereOrg = this.org.get(this.auth.user().ws_ou()).shortname();
}
open(options: NgbModalOptions = {}): Observable<any> {
+ let obs: Observable<any>;
+
+ // Load some data before opening.
+ if (this.newXact) {
+
+ obs = this.pcrud.retrieve('au', this.patronId,
+ {flesh: 1, flesh_fields: {au: ['card']}}
+ ).pipe(tap(user => this.patron = user));
+
+ } else {
+
+ obs = this.pcrud.retrieve('mbt', this.xactId, {
+ flesh: 2,
+ flesh_fields: {
+ mbt: ['usr', 'summary', 'circulation'],
+ au: ['card']
+ }
+ }).pipe(tap(xact => {
+ this.xact = xact;
+ this.patron = xact.usr();
+ }));
+ }
- // 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);
- }));
+ return obs.pipe(switchMap(_ => super.open(options)));
}
isRenewal(): boolean {
}
submit() {
+ const promise = this.newXact ?
+ this.createGroceryXact() : Promise.resolve(this.xactId);
+
+ let xactId;
+ promise.then(id => {
+ xactId = id;
+ return this.createBill(id)
+ })
+ .then(billId => this.close({xactId: xactId, billId: billId}));
+ }
+
+ handleResponse(id: number): number {
+ const evt = this.evt.parse(id);
+ if (evt) {
+ console.error(evt);
+ alert(evt);
+ return null;
+ } else {
+ return id;
+ }
+ }
+
+ createGroceryXact(): Promise<number> {
+ const groc = this.idl.create('mg');
+ groc.billing_location(this.auth.user().ws_ou());
+ groc.note(this.note);
+ groc.usr(this.patronId);
+
+ return this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.money.grocery.create',
+ this.auth.token(), groc
+ ).toPromise().then(xactId => this.handleResponse(xactId));
+ }
+
+ createBill(xactId: number): Promise<number> {
+ if (!xactId) { return Promise.reject('no xact'); }
+
const bill = this.idl.create('mb');
- bill.xact(this.xactId);
+ bill.xact(xactId);
bill.amount(this.amount);
bill.btype(this.billingType.id);
bill.billing_type(this.billingType.label);
bill.note(this.note);
- this.net.request(
+ return 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);
- }
- });
+ ).toPromise().then(billId => this.handleResponse(billId));
}
}