open-learning-exchange/planet

View on GitHub
src/app/teams/teams-view-finances.component.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { Component, Input, OnChanges, EventEmitter, Output, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table';
import { map } from 'rxjs/operators';
import { TeamsService } from './teams.service';
import { CouchService } from '../shared/couchdb.service';
import { CustomValidators } from '../validators/custom-validators';
import { PlanetMessageService } from '../shared/planet-message.service';
import { DialogsFormService } from '../shared/dialogs/dialogs-form.service';
import { DialogsLoadingService } from '../shared/dialogs/dialogs-loading.service';
import { DialogsPromptComponent } from '../shared/dialogs/dialogs-prompt.component';
import { millisecondsToDay } from '../meetups/constants';
import { StateService } from '../shared/state.service';
import { CsvService } from '../shared/csv.service';

@Component({
  selector: 'planet-teams-view-finances',
  styleUrls: [ './teams-view-finances.scss' ],
  templateUrl: './teams-view-finances.component.html',
})
export class TeamsViewFinancesComponent implements OnInit, OnChanges {

  @Input() finances: any[] = [];
  @Input() team: any = {};
  @Input() getMembers;
  @Input() editable = true;
  @Output() financesChanged = new EventEmitter<void>();
  table = new MatTableDataSource<any>();
  displayedColumns = [ 'date', 'description', 'credit', 'debit', 'balance' ];
  deleteDialog: any;
  dateNow: any;
  startDate: Date;
  endDate: Date;
  emptyTable = true;
  showBalanceWarning = false;
  curCode = this.stateService.configuration.currency || {};

  constructor(
    private csvService: CsvService,
    private couchService: CouchService,
    private dialog: MatDialog,
    private dialogsFormService: DialogsFormService,
    private dialogsLoadingService: DialogsLoadingService,
    private planetMessageService: PlanetMessageService,
    private stateService: StateService,
    private teamsService: TeamsService
  ) {
    this.couchService.currentTime().subscribe((date) => this.dateNow = date);
  }

  ngOnInit() {
    this.table.filterPredicate = (data: any, filter) => {
      const fromDate = this.startDate || -Infinity;
      const toDate = this.endDate ? this.endDate.getTime() + millisecondsToDay : Infinity;
      return data.date >= fromDate && data.date < toDate || data.date === $localize`Total`;
    };
    this.table.connect().subscribe(transactions => {
      if (transactions.length > 0 && transactions[0].filter !== this.filterString()) {
        transactions[0] = this.setTransactionsTable(transactions)[0];
      }
      this.showBalanceWarning = (this.finances && this.finances.length) === (this.table.filteredData.length - 1) &&
        transactions[0].balance < 0;
    });
  }

  ngOnChanges() {
    if (this.editable !== this.displayedColumns.indexOf('action') > -1) {
      this.displayedColumns = [ ...this.displayedColumns, this.editable ? 'action' : [] ].flat();
    }
    if (this.finances) {
      this.table.data = this.setTransactionsTable(this.finances);
    }
  }

  private setTransactionsTable(transactions: any[]): any[] {
    const financeData = transactions.filter(transaction => transaction.status !== 'archived' && transaction.date !== 'Total')
      // Overwrite values for credit and debit from early document versions on database
      .map(transaction => ({ ...transaction, credit: 0, debit: 0, [transaction.type]: transaction.amount }))
      .sort((a, b) => a.date - b.date).reduce(this.combineTransactionData, []).reverse();
    if (financeData.length === 0) {
      this.emptyTable = true;
      return [ { date: $localize`Total` } ];
    }
    this.emptyTable = false;
    const { totalCredits: credit, totalDebits: debit, balance } = financeData[0];
    return [ { date: 'Total', credit, debit, balance, filter: this.filterString() }, ...financeData ];
  }

  private filterString() {
    return (this.startDate || '').toString() + (this.endDate || '').toString();
  }

  transactionFilter() {
    this.table.filter = ' ';
  }

  private combineTransactionData(newArray: any[], transaction: any, index: number) {
    const undefinedToNumber = (value: number | undefined) => value || 0;
    const previousValue = index !== 0 ? newArray[index - 1] : { balance: 0, totalCredits: 0, totalDebits: 0 };
    return [
      ...newArray,
      {
        ...transaction,
        balance: previousValue.balance + undefinedToNumber(transaction.credit) - undefinedToNumber(transaction.debit),
        totalCredits: previousValue.totalCredits + undefinedToNumber(transaction.credit),
        totalDebits: previousValue.totalDebits + undefinedToNumber(transaction.debit),
      }
    ];
  }


  openEditTransactionDialog(transaction: any = {}) {
    this.couchService.currentTime().subscribe((time: number) => {
      this.dialogsFormService.openDialogsForm(
        transaction._id ? $localize`Edit Transaction` : $localize`Add Transaction`,
        [
          {
            name: 'type', placeholder: $localize`Type`, type: 'selectbox',
            options: [ { value: 'credit', name: $localize`Credit` }, { value: 'debit', name: $localize`Debit` } ], required: true
          },
          { name: 'description', placeholder: $localize`Note`, type: 'textbox', required: true },
          { name: 'amount', placeholder: $localize`Amount`, type: 'textbox', inputType: 'number', required: true },
          { name: 'date', placeholder: $localize`Date`, type: 'date', required: true }
        ],
        {
          type: [ transaction.type || 'credit', CustomValidators.required ],
          description: [ transaction.description || '', CustomValidators.required ],
          amount: [ transaction.amount || '', [ CustomValidators.integerValidator, CustomValidators.positiveNumberValidator ] ],
          date: [ transaction.date ? new Date(new Date(transaction.date).setHours(0, 0, 0)) : new Date(time), CustomValidators.required ]
        },
        {
          onSubmit: (newTransaction) => this.submitTransaction(newTransaction, transaction).subscribe(() => {
            this.planetMessageService.showMessage(transaction._id ? $localize`Transaction Updated` : $localize`Transaction Added`);
            this.dialogsFormService.closeDialogsForm();
          })
        }
      );
    });
  }

  submitTransaction(newTransaction, oldTransaction) {
    const { _id: teamId, teamType, teamPlanetCode } = this.team;
    const amount = +(newTransaction.amount);
    const date = new Date(newTransaction.date).getTime();
    const transaction = {
      ...oldTransaction,
      ...newTransaction,
      date,
      amount,
      docType: 'transaction',
      teamId,
      teamType,
      teamPlanetCode
    };
    return this.teamsService.updateTeam(transaction).pipe(map(() => {
      this.financesChanged.emit();
      this.dialogsLoadingService.stop();
    }));
  }

  openArchiveTransactionDialog(transaction) {
    this.deleteDialog = this.dialog.open(DialogsPromptComponent, {
      data: {
        okClick: this.archiveTransaction(transaction),
        changeType: 'delete',
        type: 'transaction',
        displayName: transaction.description
      }
    });
  }

  archiveTransaction(transaction) {
    return {
      request: this.submitTransaction(transaction, { status: 'archived' }),
      onNext: () => {
        this.deleteDialog.close();
        this.planetMessageService.showMessage('You have deleted a transaction.');
      },
      onError: () => this.planetMessageService.showAlert('There was a problem deleting this transaction.')
    };
  }

  resetDateFilter() {
    this.startDate = undefined;
    this.endDate = undefined;
    this.table.filter = '';
    this.emptyTable = this.table.data.length <= 1;
  }

  exportTableData() {
    let updatedData = [ ...this.table.filteredData ];
    updatedData.shift();

    updatedData = updatedData.map(row => ({
      date: row.date,
      description: row.description,
      credit: row.credit,
      debit: row.debit,
      balance: row.balance
    }));

    this.csvService.exportCSV({
      data: updatedData,
      title: $localize`Finances Transactions for ${this.team.name} Enterprise`
    });
  }

}