From 1ae0dae4e6ecb11bca7ab6870bdc7a4af9216660 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 1 Mar 2021 17:15:07 -0500 Subject: [PATCH] LP1904036 add billings and more Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- .../src/app/staff/circ/patron/items.component.html | 6 +- .../src/app/staff/circ/patron/items.component.ts | 21 ++-- .../src/app/staff/circ/patron/patron.service.ts | 10 +- .../staff/share/circ/billing-dialog.component.html | 80 +++++++++++++++ .../staff/share/circ/billing-dialog.component.ts | 108 +++++++++++++++++++++ .../eg2/src/app/staff/share/circ/circ.module.ts | 7 +- .../eg2/src/app/staff/share/circ/circ.service.ts | 25 ++++- .../src/app/staff/share/circ/grid.component.html | 13 ++- .../eg2/src/app/staff/share/circ/grid.component.ts | 41 +++++++- 9 files changed, 288 insertions(+), 23 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.ts diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.html index b890ff429a..04c388aa76 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.html @@ -15,7 +15,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -45,7 +45,7 @@ - + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts index 40ee8fd918..d645033523 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/items.component.ts @@ -42,6 +42,7 @@ export class ItemsComponent implements OnInit, AfterViewInit { displayClaimsReturned: number = null; fetchCheckedIn = true; displayAltList = true; + persistKey: string; @ViewChild('checkoutsGrid') private checkoutsGrid: CircGridComponent; @ViewChild('otherGrid') private otherGrid: CircGridComponent; @@ -61,23 +62,29 @@ export class ItemsComponent implements OnInit, AfterViewInit { ) {} ngOnInit() { - this.load(); + this.load(true); } ngAfterViewInit() { } - load(): Promise { + load(firstLoad?: boolean): Promise { this.loading = true; - return this.applyDisplaySettings() - .then(_ => this.loadTab(this.itemsTab)); + + if (firstLoad) { + return this.applyDisplaySettings() + .then(_ => this.loadTab(this.itemsTab)); + } else { + return this.loadTab(this.itemsTab) + .then(_ => this.context.refreshPatron()); + } } tabChange(evt: NgbNavChangeEvent) { setTimeout(() => this.loadTab(evt.nextId)); } - loadTab(name: string) { + loadTab(name: string): Promise { this.loading = true; let promise; if (name === 'checkouts') { @@ -88,7 +95,9 @@ export class ItemsComponent implements OnInit, AfterViewInit { promise = this.loadNonCatGrid(); } - promise.then(_ => this.loading = false); + this.persistKey = `circ.patron.items.${name}`; + + return promise.then(_ => this.loading = false); } applyDisplaySettings(): Promise { diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts index 4e6fcb42e0..8eb24abfbd 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts @@ -110,6 +110,13 @@ export class PatronContextService { this.loaded = false; this.patron = null; this.checkouts = []; + return this.refreshPatron(id).then(_ => this.loaded = true); + } + + // Update the patron data without resetting all of the context data. + refreshPatron(id?: number): Promise { + if (!id) { id = this.patron.id(); } + this.alerts = new PatronAlerts(); return this.net.request( @@ -118,8 +125,7 @@ export class PatronContextService { this.auth.token(), id, PATRON_FLESH_FIELDS).toPromise() .then(p => this.patron = p) .then(_ => this.getPatronStats(id)) - .then(_ => this.compileAlerts()) - .then(_ => this.loaded = true); + .then(_ => this.compileAlerts()); } getPatronStats(id: number): Promise { 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 new file mode 100644 index 0000000000..c957ed674d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.html @@ -0,0 +1,80 @@ + + + + + + + + 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 new file mode 100644 index 0000000000..6b20e19dfc --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/billing-dialog.component.ts @@ -0,0 +1,108 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {switchMap} from 'rxjs/operators'; +import {IdlObject} 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 */ + +@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 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 = 101; // Stock "Misc" + 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() { + this.close(); + } +} + 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 a0675888d5..3cf3790d16 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 @@ -8,6 +8,7 @@ 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'; @NgModule({ declarations: [ @@ -16,7 +17,8 @@ import {CircEventsComponent} from './events-dialog.component'; DueDateDialogComponent, PrecatCheckoutDialogComponent, ClaimsReturnedDialogComponent, - CircEventsComponent + CircEventsComponent, + AddBillingDialogComponent ], imports: [ StaffCommonModule, @@ -24,7 +26,8 @@ import {CircEventsComponent} from './events-dialog.component'; ], exports: [ CircGridComponent, - CircComponentsComponent + CircComponentsComponent, + AddBillingDialogComponent ], 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 3b84242d30..30f8699437 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 @@ -157,6 +157,7 @@ export class CircService { components: CircComponentsComponent; nonCatTypes: IdlObject[] = null; + billingTypes: IdlObject[] = null; autoOverrideCheckoutEvents: {[textcode: string]: boolean} = {}; suppressCheckinPopups = false; ignoreCheckinPrecats = false; @@ -184,6 +185,21 @@ 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( @@ -410,8 +426,7 @@ export class CircService { case 'SUCCESS': case 'NO_CHANGE': this.audio.play('success.checkin'); - // TODO do copy status stuff - break; + return this.handleCheckinSuccess(result); case 'ITEM_NOT_CATALOGED': this.audio.play('error.checkout.no_cataloged'); @@ -421,13 +436,15 @@ export class CircService { return this.components.routeToCatalogingDialog.open() .toPromise().then(_ => result); } - - // alert, etc. } return Promise.resolve(result); } + handleCheckinSuccess(result: CheckinResult): Promise { + return Promise.resolve(result); + } + handleOverridableCheckinEvents( result: CheckinResult, events: EgEvent[]): Promise { const params = result.params; diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html index b89311a4b2..28353aecee 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html @@ -17,6 +17,7 @@ dialogTitle="Claims Never Checked Out" dialogBody="Mark {{claimsNeverCount}} items as Never Checked Out?"> + @@ -34,9 +35,8 @@ + [rowClassCallback]="rowClass" [persistKey]="persistKey" + [useLocalSort]="true" [cellTextGenerator]="cellTextGenerator"> + + + 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 cd22019899..59b57d9029 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,6 +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'; export interface CircGridEntry { index: string; // class + id -- row index @@ -113,6 +114,8 @@ export class CircGridComponent implements OnInit { private progressDialog: ProgressDialogComponent; @ViewChild('claimsReturnedDialog') private claimsReturnedDialog: ClaimsReturnedDialogComponent; + @ViewChild('addBillingDialog') + private addBillingDialog: AddBillingDialogComponent; constructor( private org: OrgService, @@ -132,7 +135,12 @@ export class CircGridComponent implements OnInit { // The grid never fetches data directly. // The caller is responsible initiating all data loads. this.gridDataSource.getRows = (pager: Pager, sort: any[]) => { - return this.entries ? from(this.entries) : empty(); + if (!this.entries) { return empty(); } + + const page = this.entries.slice(pager.offset, pager.offset + pager.limit) + .filter(entry => entry !== undefined); + + return from(page); }; this.cellTextGenerator = { @@ -367,7 +375,14 @@ export class CircGridComponent implements OnInit { if (!circ) { return false; } // noncat if (row.overdue === undefined) { - row.overdue = (Date.parse(circ.due_date()) < this.nowDate); + + if (circ.stop_fines() && + // Items that aren't really checked out can't be overdue. + circ.stop_fines().match(/LOST|CLAIMSRETURNED|CLAIMSNEVERCHECKEDOUT/)) { + row.overdue = false; + } else { + row.overdue = (Date.parse(circ.due_date()) < this.nowDate); + } } return row.overdue; } @@ -567,5 +582,27 @@ export class CircGridComponent implements OnInit { ); }); } + + openBillingDialog(rows: CircGridEntry[]) { + + let changesApplied = false; + + from(this.getCircIds(rows)) + .pipe(concatMap(id => { + this.addBillingDialog.xactId = id; + return this.addBillingDialog.open(); + })) + .subscribe( + changes => { + if (changes) { changesApplied = true; } + }, + err => this.reportError(err), + () => { + if (changesApplied) { + this.emitReloadRequest(); + } + } + ); + } } -- 2.11.0