--- /dev/null
+<eg-string #successString i18n-text text="Rollover Succeeded"></eg-string>
+<eg-string #updateFailedString i18n-text text="Rollover Failed"></eg-string>
+
+<ng-template #dialogContent>
+ <div class="modal-header bg-info" *ngIf="doneLoading">
+ <h3 class="modal-title" i18n>Fund Propagation and Rollover</h3>
+ <button type="button" class="close"
+ [disabled]="isProcessing"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body" [hidden]="!doneLoading">
+ <form #rolloverForm="ngForm" role="form" class="form-validated">
+ <div class="row col" i18n>Context Org Unit: {{contextOrg?.shortname()}}</div>
+ <div class="row col">
+ <label for="year" class="my-1 mr-1" i18n>Year</label>
+ <eg-combobox [entries]="years" *ngIf="years"
+ [required]="true" [selectedId]="year"
+ (onChange)="year = $event ? $event.id : null">
+ </eg-combobox>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" name="includeDescendants" id="includeDescendants"
+ class="form-check-input"
+ [(ngModel)]="includeDescendants">
+ <label for="includeDescendants" class="form-check-label" i18n>Include funds from descendant Org Units</label>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" name="propgateFunds" id="propgateFunds"
+ class="form-check-input"
+ [(ngModel)]="propgateFunds">
+ <label for="propgateFunds" class="form-check-label" i18n>Propagate Funds</label>
+ <eg-help-popover helpText="Propagate Funds creates new funds for the next fiscal year. Propagating funds will not affect the money or encumbrances in the funds. Only funds that have the Propagate setting enabled will be affected." i18n-helpText></eg-help-popover>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" name="doCloseout" id="doCloseout"
+ class="form-check-input"
+ [(ngModel)]="doCloseout">
+ <label for="doCloseout" class="form-check-label" i18n>Perform Fiscal Year Close-Out</label>
+ <eg-help-popover helpText="Perform Fiscal Year Close-Out moves encumbrances to the corresponding fund for the next fiscal year and deactivates funds for the selected fiscal year. If funds have the Rollover setting enabled, all unspect money will also be moved to the corresponding fund for the next fiscal year." i18n-helpText></eg-help-popover>
+ <span class="alert-warning" *ngIf="doCloseout && !dryRun">Will do a Close-Out for real. If you need to double-check first, check the "Dry Run" checkbox.</span>
+ </div>
+ <div class="offset-sm-1 form-check">
+ <input type="checkbox" name="limitToEncumbrances" id="limitToEncumbrances"
+ class="form-check-input"
+ [(ngModel)]="limitToEncumbrances">
+ <label for="limitToEncumbrances" class="form-check-label" i18n>Limit Fiscal Year Close-Out to Encumbrances</label>
+ <eg-help-popover helpText="This option will limit the Perform Fiscal Year Close-Out procedure to only move encumbrances to the corresponding fund for the next fiscal year. Any unspent money in the funds will not roll over." i18n-helpText></eg-help-popover>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" name="dryRun" id="dryRun"
+ class="form-check-input"
+ [(ngModel)]="dryRun">
+ <label for="dryRun" class="form-check-label" i18n>Dry Run — no data will be changed</label>
+ <eg-help-popover helpText="Select Dry Run to see a preview of the changes that would occur based on the selected actions. Data will not be changed when Dry Run is selected." i18n-helpText></eg-help-popover>
+ </div>
+ </form>
+ <div class="row" [hidden]="!isProcessing">
+ <div class="col-lg-10 offset-lg-1">
+ <eg-progress-inline #rolloverProgress></eg-progress-inline>
+ </div>
+ </div>
+ <div [hidden]="!showResults" class="row col mt-2">
+ <h4 i18n>Fund Propagation & Rollover Summary for Fiscal Year {{year + 1}}</h4>
+ <ul>
+ <li *ngIf="dryRun" i18n>DRY RUN: these changes have not been committed yet.</li>
+ <li i18n>{{count}} funds propagated for fiscal year {{year + 1}} for the selected locations</li>
+ <li i18n>${{amount_rolled}} unspent money rolled over to fiscal year {{year + 1}} for the selected locations</li>
+ </ul>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-info"
+ [disabled]="isProcessing"
+ (click)="rollover()" i18n>Process</button>
+ <button type="button" class="btn btn-warning"
+ [disabled]="isProcessing"
+ (click)="close()" i18n>Close</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm} from '@angular/forms';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {Pager} from '@eg/share/util/pager';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PermService} from '@eg/core/perm.service';
+import {OrgService} from '@eg/core/org.service';
+import {Observable} from 'rxjs';
+import {map} from 'rxjs/operators';
+import {ProgressInlineComponent} from '@eg/share/dialog/progress-inline.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+ selector: 'eg-fund-rollover-dialog',
+ templateUrl: './fund-rollover-dialog.component.html'
+})
+
+export class FundRolloverDialogComponent
+ extends DialogComponent implements OnInit {
+
+ doneLoading: boolean = false;
+
+ @Input() contextOrgId: number;
+
+ @ViewChild('successString', { static: true }) successString: StringComponent;
+ @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent;
+ @ViewChild('rolloverProgress', { static: true })
+ private rolloverProgress: ProgressInlineComponent;
+
+ includeDescendants: boolean = false;
+ propagateFunds: boolean = false;
+ doCloseout: boolean = false;
+ limitToEncumbrances: boolean = false;
+ dryRun: boolean = true;
+ contextOrg: IdlObject;
+ isProcessing: boolean = false;
+ showResults: boolean = false;
+ years: ComboboxEntry[];
+ year: number;
+
+ count: number;
+ amount_rolled: number;
+
+ constructor(
+ private idl: IdlService,
+ private evt: EventService,
+ private net: NetService,
+ private auth: AuthService,
+ private pcrud: PcrudService,
+ private perm: PermService,
+ private toast: ToastService,
+ private org: OrgService,
+ private modal: NgbModal
+ ) {
+ super(modal);
+ }
+
+ ngOnInit() {
+ this.onOpen$.subscribe(() => this._initDialog());
+ this.doneLoading = true;
+ }
+
+ private _initDialog() {
+ this.contextOrg = this.org.get(this.contextOrgId);
+ this.includeDescendants = false;
+ this.propagateFunds = false;
+ this.doCloseout = false;
+ this.limitToEncumbrances = false;
+ this.showResults = false;
+ this.dryRun = true;
+ this.years = null;
+ this.year = null;
+ let maxYear = 0;
+ this.net.request(
+ 'open-ils.acq',
+ 'open-ils.acq.fund.org.years.retrieve',
+ this.auth.token(),
+ {},
+ { limit_perm: 'VIEW_FUND' }
+ ).subscribe(
+ years => {
+ this.years = years.map(y => {
+ if (maxYear < y) { maxYear = y; }
+ return { id: y, label: y };
+ });
+ this.year = maxYear;
+ }
+ )
+ }
+
+ rollover() {
+ this.isProcessing = true;
+
+ const rolloverResponses: any = [];
+
+ let method = 'open-ils.acq.fiscal_rollover';
+ if (this.doCloseout) {
+ method += '.combined';
+ } else {
+ method += '.propagate';
+ }
+ if (this.dryRun) { method += '.dry_run'; }
+
+ this.count = 0;
+ this.amount_rolled = 0;
+
+ this.net.request(
+ 'open-ils.acq',
+ method,
+ this.auth.token(),
+ this.year,
+ this.contextOrgId,
+ this.includeDescendants,
+ { encumb_only : this.limitToEncumbrances }
+ ).subscribe(
+ r => {
+ rolloverResponses.push(r.fund);
+ this.count++;
+ this.amount_rolled += Number(r.rollover_amount);
+ },
+ err => {},
+ () => {
+ this.isProcessing = false;
+ this.showResults = true;
+ // note that we're intentionally not closing the dialog
+ // so that user can view the results
+ }
+ )
+ }
+
+}
<eg-grid-toolbar-button [disabled]="!canCreate"
label="New {{idlClassDef.label}}" i18n-label (onClick)="createNew()">
</eg-grid-toolbar-button>
+ <eg-grid-toolbar-button [disabled]="!canRollover"
+ label="Fiscal Propagation and Rollover" i18n-label (onClick)="doRollover()">
+ </eg-grid-toolbar-button>
<eg-grid-toolbar-action label="View Selected" i18n-label
(onClick)="openFundDetailsDialog($event)">
</eg-grid-toolbar-action>
</eg-fm-record-editor>
<eg-fund-details-dialog #fundDetailsDialog></eg-fund-details-dialog>
+<eg-fund-rollover-dialog #fundRolloverDialog></eg-fund-rollover-dialog>
import {NetService} from '@eg/core/net.service';
import {StringComponent} from '@eg/share/string/string.component';
import {FundDetailsDialogComponent} from './fund-details-dialog.component';
+import {FundRolloverDialogComponent} from './fund-rollover-dialog.component';
@Component({
selector: 'eg-funds-manager',
classLabel: string;
@ViewChild('fundDetailsDialog', { static: false }) fundDetailsDialog: FundDetailsDialogComponent;
+ @ViewChild('fundRolloverDialog', { static: false }) fundRolloverDialog: FundRolloverDialogComponent;
@ViewChild('grid', { static: true }) grid: GridComponent;
cellTextGenerator: GridCellTextGenerator;
+ canRollover: boolean = false;
constructor(
route: ActivatedRoute,
auth: AuthService,
pcrud: PcrudService,
perm: PermService,
+ private perm2: PermService, // need copy because perm is private to base
+ // component
toast: ToastService,
private net: NetService
) {
this.cellTextGenerator = {
name: row => row.name()
};
+ this.checkRolloverPerms();
this.fieldOrder = 'name,code,year,org,active,currency_type,balance_stop_percentage,balance_warning_percentage,propagate,rollover';
this.defaultNewRecord = this.idl.create('acqf');
this.defaultNewRecord.active(true);
this.includeOrgDescendants = true;
}
+ checkRolloverPerms() {
+ this.canRollover = false;
+
+ this.perm2.hasWorkPermAt(['ADMIN_FUND'], true).then(permMap => {
+ Object.keys(permMap).forEach(key => {
+ if (permMap[key].length > 0) {
+ this.canRollover = true;
+ }
+ });
+ });
+ }
+
openFundDetailsDialog(rows: IdlObject[]) {
if (rows.length > 0) {
this.fundDetailsDialog.fundId = rows[0].id();
getDefaultYear(): string {
return new Date().getFullYear().toString();
}
+
+ doRollover() {
+ this.fundRolloverDialog.contextOrgId = this.searchOrgs.primaryOrgId;
+ this.fundRolloverDialog.open({size: 'lg'}).subscribe(
+ ok => {},
+ err => {},
+ () => this.grid.reload()
+ );
+ }
}
import {FundingSourceTransactionsDialogComponent} from './funding-source-transactions-dialog.component';
import {FundTagsComponent} from './fund-tags.component';
import {FundTransferDialogComponent} from './fund-transfer-dialog.component';
+import {FundRolloverDialogComponent} from './fund-rollover-dialog.component';
@NgModule({
declarations: [
FundingSourcesComponent,
FundingSourceTransactionsDialogComponent,
FundTagsComponent,
- FundTransferDialogComponent
+ FundTransferDialogComponent,
+ FundRolloverDialogComponent
],
imports: [
StaffCommonModule,
AdminCommonModule,
- FundsRoutingModule
+ FundsRoutingModule,
],
exports: [
],