--- /dev/null
+<ng-template #dialogContent>
+ <form class="form-validated">
+ <div class="modal-header bg-info">
+ <h3 class="modal-title" i18n>Add Items to Selected Line Items</h3>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <h4 i18n>Lineitem(s) selected:
+ <span *ngFor="let id of ids; last as isLast">
+ {{id}}<span *ngIf="!isLast">,</span>
+ </span>
+ </h4>
+ <eg-lineitem-copies (lineitemWithCopies)="lineitemWithCopies = $event" mode="multiAdd"></eg-lineitem-copies>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close(lineitemWithCopies)" i18n>Apply</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Exit Dialog</button>
+ </div>
+ </form>
+</ng-template>
+
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+ selector: 'eg-acq-add-copies-dialog',
+ templateUrl: './add-copies-dialog.component.html'
+})
+
+export class AddCopiesDialogComponent extends DialogComponent {
+ @Input() ids: number[];
+ lineitemWithCopies: IdlObject;
+ constructor(private modal: NgbModal) { super(modal); }
+}
+
+
<div class="flex-1 p-1" i18n>Collection Code</div>
<div class="flex-1 p-1" i18n>Fund</div>
<div class="flex-1 p-1" i18n>Circ Modifier</div>
- <div class="flex-1 p-1" i18n>Callnumber</div>
+ <div class="flex-1 p-1" *ngIf="!batchAdd" i18n>Callnumber</div>
<div class="flex-1 p-1" i18n>
- <ng-container *ngIf="!hideBarcode">Barcode</ng-container>
+ <ng-container *ngIf="!hideBarcode && !batchAdd">Barcode</ng-container>
</div>
<div class="flex-1 p-1"></div>
<div class="flex-1 p-1"></div>
<div class="pt-2 bg-light border border-secondary border-top-0 rounded-bottom">
<eg-lineitem-copy-attrs (batchApplyRequested)="batchApplyAttrs($event)"
+ [batchAdd]="batchAdd"
[batchMode]="true"> </eg-lineitem-copy-attrs>
</div>
</ng-container>
<div class="batch-copy-row"
*ngFor="let copy of copies(); let idx = index">
<eg-lineitem-copy-attrs
+ [batchAdd]="batchAdd"
(receiveRequested)="receiveCopy($event)"
(unReceiveRequested)="unReceiveCopy($event)"
(deleteRequested)="deleteCopy($event)"
export class LineitemBatchCopiesComponent implements OnInit {
@Input() lineitem: IdlObject;
+ @Input() batchAdd = false;
@ViewChild('confirmAlertsDialog') confirmAlertsDialog: ConfirmDialogComponent;
@ViewChild('cancelDialog') cancelDialog: CancelDialogComponent;
private liService: LineitemService
) {}
- ngOnInit() {}
+ ngOnInit() {
+ if (!this.lineitem) {
+ this.lineitem = this.idl.create('jub');
+ const copy = this.idl.create('acqlid');
+ copy.isnew(true);
+ this.lineitem.lineitem_details([copy]);
+ }
+ }
// Propagate values from the batch edit bar into the indivudual LID's
batchApplyAttrs(copyTemplate: IdlObject) {
[disabled]="!distribFormCbox.selectedId || liLocked"
(click)="applyFormula(distribFormCbox.selectedId)" i18n>Apply</button>
- <button class="btn btn-sm btn-success ml-auto" [disabled]="liLocked"
+ <button class="btn btn-sm btn-success ml-auto" [disabled]="liLocked" *ngIf="mode !== 'multiAdd'"
(click)="save()" i18n>Save Changes</button>
</div>
<ng-container *ngIf="lineitem && !saving">
- <div class="card tight-card" *ngIf="lineitem.distribution_formulas().length">
+ <div class="card tight-card" *ngIf="lineitem.distribution_formulas().length && mode !== 'multiAdd'">
<div class="card-header" i18n>Distribution formulas applied to this lineitem</div>
<div class="card-body">
<ul class="p-0 m-0">
</div>
</div>
- <eg-lineitem-batch-copies [lineitem]="lineitem"></eg-lineitem-batch-copies>
+ <eg-lineitem-batch-copies [lineitem]="lineitem" [batchAdd]="mode === 'multiAdd'"></eg-lineitem-batch-copies>
</ng-container>
}
@Component({
+ selector: 'eg-lineitem-copies',
templateUrl: 'copies.component.html'
})
export class LineitemCopiesComponent implements OnInit, AfterViewInit {
+
static newCopyId = -1;
+ // modes are 'normal' and 'multiAdd'
+ // normal = manage copies for a single line item whose
+ // ID is taken from the route
+ // multiAdd = embedded in a modal and applying the results
+ // to selected LIs
+ @Input() mode = 'normal';
+
+ // emited only in multiAdd mode
+ @Output() lineitemWithCopies = new EventEmitter<IdlObject>();
+
lineitemId: number;
lineitem: IdlObject;
copyCount = 1;
this.formulaFilter.owner =
this.org.fullPath(this.auth.user().ws_ou(), true);
- this.route.paramMap.subscribe((params: ParamMap) => {
- const id = +params.get('lineitemId');
- if (id !== this.lineitemId) {
- this.lineitemId = id;
- if (id) { this.load(); }
- }
- });
+ if (this.mode === 'multiAdd') {
+ this.load();
+ } else {
+ // normal mode, we're checking the route to initalize
+ // ourselves
+ this.route.paramMap.subscribe((params: ParamMap) => {
+ const id = +params.get('lineitemId');
+ if (id !== this.lineitemId) {
+ this.lineitemId = id;
+ if (id) { this.load(); }
+ }
+ });
+ }
this.liService.getLiAttrDefs();
}
params = {toCache: true, fromCache: true};
}
- return this.liService.getFleshedLineitems([this.lineitemId], params)
- .pipe(tap(liStruct => this.lineitem = liStruct.lineitem)).toPromise()
- .then(_ => {
- this.liLocked =
- this.lineitem.state().match(/on-order|received|cancelled/);
- })
- .then(_ => this.applyCount());
+ if (this.mode === 'multiAdd') {
+ this.lineitem = this.idl.create('jub');
+ this.lineitem.lineitem_details([]);
+ this.lineitem.distribution_formulas([]);
+ this.liLocked = false; // trusting our invoker in multiAdd mode
+ this.applyCount();
+ this.lineitemWithCopies.emit(this.lineitem);
+ return Promise.resolve(true);
+ } else {
+ return this.liService.getFleshedLineitems([this.lineitemId], params)
+ .pipe(tap(liStruct => this.lineitem = liStruct.lineitem)).toPromise()
+ .then(_ => {
+ this.liLocked =
+ this.lineitem.state().match(/on-order|received|cancelled/);
+ })
+ .then(_ => this.applyCount());
+ }
}
ngAfterViewInit() {
app.creator(this.auth.user().id());
app.formula(formula.id());
- this.pcrud.create(app).toPromise().then(a => {
- a.creator(this.auth.user());
- a.formula(formula);
- this.lineitem.distribution_formulas().push(a);
- });
+ if (this.mode === 'multiAdd') {
+ app.isnew(true);
+ this.lineitem.distribution_formulas().push(app);
+ } else {
+ this.pcrud.create(app).toPromise().then(a => {
+ a.creator(this.auth.user());
+ a.formula(formula);
+ this.lineitem.distribution_formulas().push(a);
+ });
+ }
}
// Grab values applied by distribution formulas and cache them before
(onChange)="valueChange('circ_modifier', $event)">
</eg-combobox>
</div>
- <div class="flex-1 p-1">
+ <div class="flex-1 p-1" *ngIf="!batchAdd">
<ng-container *ngIf="fieldIsDisabled('cn_label')">
<span>{{copy.cn_label()}}</span>
</ng-container>
<button class="btn btn-outline-dark"
(click)="batchApplyRequested.emit(copy)" i18n>Batch Update</button>
</ng-container>
- <ng-container *ngIf="!batchMode">
+ <ng-container *ngIf="!batchMode && !batchAdd">
<ng-container *ngIf="fieldIsDisabled('barcode')">
<span>{{copy.barcode()}}</span>
</ng-container>
@Input() lineitem: IdlObject;
@Input() rowIndex: number;
+ @Input() batchAdd = false;
fundEntries: ComboboxEntry[];
circModEntries: ComboboxEntry[];
<!-- BATCH ACTIONS -->
<eg-acq-cancel-dialog recordType="li" #cancelDialog></eg-acq-cancel-dialog>
<eg-acq-delete-lineitems-dialog #deleteLineitemsDialog></eg-acq-delete-lineitems-dialog>
+<eg-acq-add-copies-dialog #addCopiesDialog></eg-acq-add-copies-dialog>
<eg-acq-claim-policy-dialog #claimPolicyDialog></eg-acq-claim-policy-dialog>
+<div class="col-lg-6 offset-lg-3" *ngIf="saving">
+ <eg-progress-inline [max]="progressMax" [value]="progressValue">
+ </eg-progress-inline>
+</div>
+
<div class="row mt-3" *ngIf="poId || picklistId">
<div class="col-lg-1">
<div ngbDropdown>
queryParamsHandling="merge" i18n>Add Brief Record</a>
<button ngbDropdownItem (click)="deleteLineitems()"
[disabled]="!canDeleteLis() || !selectedIds().length" i18n>Delete Selected Lineitems</button>
+ <button ngbDropdownItem (click)="addCopiesToLineitems()"
+ [disabled]="!canAddLiCopies() || !selectedIds().length" i18n>Add Items to Selected Lineitems</button>
<div class="dropdown-divider"></div>
<h6 class="dropdown-header" i18n>Selection List Actions</h6>
<button ngbDropdownItem (click)="createPo()"
import {Component, OnInit, Input, Output, ViewChild} from '@angular/core';
import {Router, ActivatedRoute, ParamMap} from '@angular/router';
-import {Observable, from, of} from 'rxjs';
+import {Observable, from, of, Subscription} from 'rxjs';
import {tap, concatMap} from 'rxjs/operators';
import {Pager} from '@eg/share/util/pager';
import {EgEvent, EventService} from '@eg/core/event.service';
-import {IdlObject} from '@eg/core/idl.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
import {NetService} from '@eg/core/net.service';
import {AuthService} from '@eg/core/auth.service';
import {ServerStoreService} from '@eg/core/server-store.service';
import {LineitemService} from './lineitem.service';
+import {PoService} from '../po/po.service';
import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
import {CancelDialogComponent} from './cancel-dialog.component';
import {DeleteLineitemsDialogComponent} from './delete-lineitems-dialog.component';
+import {AddCopiesDialogComponent} from './add-copies-dialog.component';
import {ClaimPolicyDialogComponent} from './claim-policy-dialog.component';
const DELETABLE_STATES = [
picklistId: number = null;
poId: number = null;
+ poAllowsNewCopies = false;
+ poSubscription: Subscription;
recordId: number = null; // lineitems related to a bib.
loading = false;
pageOfLineitems: IdlObject[] = [];
lineitemIds: number[] = [];
+ saving = false;
+ progressMax = 0;
+ progressValue = 0;
+
// Selected lineitems
selected: {[id: number]: boolean} = {};
@ViewChild('cancelDialog') cancelDialog: CancelDialogComponent;
@ViewChild('deleteLineitemsDialog') deleteLineitemsDialog: DeleteLineitemsDialogComponent;
+ @ViewChild('addCopiesDialog') addCopiesDialog: AddCopiesDialogComponent;
@ViewChild('claimPolicyDialog') claimPolicyDialog: ClaimPolicyDialogComponent;
constructor(
private net: NetService,
private auth: AuthService,
private store: ServerStoreService,
+ private idl: IdlService,
private holdings: HoldingsService,
- private liService: LineitemService
+ private liService: LineitemService,
+ private poService: PoService
) {}
ngOnInit() {
});
});
+ this.poSubscription = this.poService.poRetrieved.subscribe(() => {
+ this.poAllowsNewCopies = this.po().order_date() ? false : true;
+ });
+ }
+
+ po(): IdlObject {
+ return this.poService.currentPo;
}
pageSizeChange(count: number) {
});
}
+ addCopiesToLineitems() {
+ const ids = Object.keys(this.selected).filter(id => this.selected[id]);
+
+ this.addCopiesDialog.ids = ids.map(i => Number(i));
+ this.addCopiesDialog.open({size: 'xl'}).subscribe(templateLineitem => {
+ if (!templateLineitem) { return; }
+
+ const lids = [];
+ ids.forEach(li_id => {
+ templateLineitem.lineitem_details().forEach(lid => {
+ const c = this.idl.clone(lid);
+ c.isnew(true);
+ c.lineitem(li_id);
+ lids.push(c);
+ });
+ });
+
+ this.saving = true;
+ this.progressMax = null;
+ this.progressValue = 0;
+
+ this.liService.updateLiDetailsMulti(lids).subscribe(
+ struct => {
+ this.progressMax = struct.total;
+ this.progressValue++;
+ },
+ err => {},
+ () => {
+ // Remove the modified LI's from the cache so we are
+ // forced to re-fetch them.
+ ids.forEach(id => delete this.liService.liCache[id]);
+ this.saving = false;
+ this.loadPageOfLis();
+ }
+ );
+
+ });
+ }
+
liHasRealCopies(li: IdlObject): boolean {
for (let idx = 0; idx < li.lineitem_details().length; idx++) {
if (li.lineitem_details()[idx].eg_copy_id()) {
});
}
+ // can add lineitems unless the PO has been activated at some
+ // point in the past
+ canAddLiCopies(): boolean {
+ if (this.picklistId) {
+ return true;
+ } else {
+ if (this.po()) {
+ this.poAllowsNewCopies = this.po().order_date() ? false : true;
+ }
+ return this.poAllowsNewCopies;
+ }
+ }
+
// For PO's, lineitems can only be deleted if they are pending order.
canDeleteLis(): boolean {
const li = this.pageOfLineitems[0];
} from '@eg/share/item-location-select/item-location-select.module';
import {LineitemWorksheetComponent} from './worksheet.component';
import {LineitemService} from './lineitem.service';
+import {PoService} from '../po/po.service';
import {LineitemComponent} from './lineitem.component';
import {LineitemNotesComponent} from './notes.component';
import {LineitemDetailComponent} from './detail.component';
import {BriefRecordComponent} from './brief-record.component';
import {CancelDialogComponent} from './cancel-dialog.component';
import {DeleteLineitemsDialogComponent} from './delete-lineitems-dialog.component';
+import {AddCopiesDialogComponent} from './add-copies-dialog.component';
import {ClaimPolicyDialogComponent} from './claim-policy-dialog.component';
import {MarcEditModule} from '@eg/staff/share/marc-edit/marc-edit.module';
LineitemHistoryComponent,
CancelDialogComponent,
DeleteLineitemsDialogComponent,
+ AddCopiesDialogComponent,
ClaimPolicyDialogComponent,
BriefRecordComponent,
LineitemWorksheetComponent
LineitemListComponent,
CancelDialogComponent,
DeleteLineitemsDialogComponent,
+ AddCopiesDialogComponent,
ClaimPolicyDialogComponent
],
imports: [
MarcEditModule
],
providers: [
- LineitemService
+ LineitemService,
+ PoService
]
})
));
}
+ updateLiDetailsMulti(inLids: IdlObject[]): Observable<BatchLineitemUpdateStruct> {
+ const lids = inLids.filter(copy =>
+ (copy.isnew() || copy.ischanged() || copy.isdeleted()));
+
+ return from(
+
+ // Ensure we have the updated fund/loc/mod values before
+ // sending the copies off to be updated and then re-drawn.
+ this.fetchFunds(lids.map(lid => lid.fund()))
+ .then(_ => this.fetchLocations(lids.map(lid => lid.location())))
+ .then(_ => this.fetchCircMods(lids.map(lid => lid.circ_modifier())))
+
+ ).pipe(switchMap(_ =>
+ this.net.request(
+ 'open-ils.acq',
+ 'open-ils.acq.lineitem_detail.cud.batch',
+ this.auth.token(), lids
+ )
+ ));
+ }
+
updateLineitems(lis: IdlObject[]): Observable<BatchLineitemUpdateStruct> {
// Fire updates one LI at a time. Note the API allows passing