Initial Transactions

Transactions
dan 2019-07-05 00:24:46 -06:00
parent 25f4b1d84c
commit 142c41ae8e
21 changed files with 535 additions and 25 deletions

View File

@ -12,6 +12,7 @@ import { ContactPageComponent } from './components/contact-page/contact-page.com
import { SalvationPageComponent } from './components/salvation-page/salvation-page.component';
import { CampPageComponent } from './components/camp-page/camp-page.component';
import { MembersPageComponent } from './components/members-page/members-page.component';
import { AddTransactionPageComponent } from './components/add-transaction-page/add-transaction-page.component';
const routes =
[
@ -66,6 +67,10 @@ const routes =
{
path: 'camp',
component: CampPageComponent
},
{
path: 'transactions/add',
component: AddTransactionPageComponent
}
]

View File

@ -4,15 +4,18 @@ import { WindowRefService } from './services/window-ref.service';
import { EmailService } from './services/email.service';
import { SermonService } from './services/sermon.service';
import { LoginService } from './services/login.service';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { MatButtonModule,
MatInputModule,
MatSliderModule,
MatSnackBarModule,
MatDialogModule } from '@angular/material';
MatSnackBarModule,
MatDialogModule,
MatSelectModule,
MatOptionModule,
MatAutocompleteModule} from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import 'hammerjs';
@ -63,6 +66,8 @@ import { CampPageComponent } from './components/camp-page/camp-page.component';
import { MembersPageComponent } from './components/members-page/members-page.component';
import { AddUserPopupComponent } from './components/popups/add-user-popup/add-user-popup.component';
import { UserService } from './services/user.service';
import { TransactionService } from './services/transaction.service';
import { AddTransactionPageComponent } from './components/add-transaction-page/add-transaction-page.component';
@ -105,22 +110,27 @@ import { UserService } from './services/user.service';
VideoPopupComponent,
CampPageComponent,
MembersPageComponent,
AddUserPopupComponent
AddUserPopupComponent,
AddTransactionPageComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
BrowserAnimationsModule,
//Angular Material Components
MatButtonModule,
MatSelectModule,
MatOptionModule,
MatInputModule,
MatSliderModule,
MatSnackBarModule,
MatDialogModule,
MatAutocompleteModule
],
providers: [LoginService,UserService,GoogleAnalyticsService,SermonService,EventService,EmailService,WindowRefService],
providers: [LoginService,UserService,GoogleAnalyticsService,SermonService,TransactionService,EventService,EmailService,WindowRefService],
entryComponents: [AddSermonPopupComponent,
LoginPopupComponent,
OkPopupComponent,

View File

@ -0,0 +1,56 @@
.d-inline-block {
display: inline-block;
}
.fw-b {
font-weight: bold;
}
.w-5 {
width: 5%;
}
.w-15 {
width: 15%;
}
.w-20 {
width: 20%;
}
.w-30 {
width: 30%;
}
.w-80 {
width: 80%;
}
.m-1 {
margin: 1rem;
}
.mt-1 {
margin-top: 1rem;
}
.mt-2 {
margin-top: 2rem;
}
.ml-1 {
margin-left: 1rem;
}
.ml-2 {
margin-left: 2rem;
}
.mr-1 {
margin-right: 1rem;
}
.bb-1 {
border-bottom: 1px solid #ff4081;
}

View File

@ -0,0 +1,78 @@
<div>&nbsp;</div>
<h2 class="ml-1 mr-1 bb-1">Add Transactions</h2>
<form [formGroup]="form">
<div formArrayName="contributions" *ngFor="let item of form.get('contributions').controls; let i = index;">
<div class="m-1 mt-2 bb-1" [formGroupName]="i">
<button class="w-5" mat-icon-button (click)="deleteContributor(i)">
<i ofbicon>delete_forever</i>
</button>
<mat-form-field class="w-15">
<input matInput placeholder="Date" type="date" formControlName="date" >
</mat-form-field>
<mat-form-field class="w-80">
<input type="text" placeholder="Contributor" aria-label="Contributor" matInput formControlName="contributorId" (ngModelChange)="onContributorChange($event)" [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="contirbutorDisplayFn">
<mat-option *ngFor="let option of filteredContributors" [value]="option">
{{option.display}}
</mat-option>
</mat-autocomplete>
</mat-form-field>
<div formArrayName="transactions" *ngFor="let trans of item.get('transactions').controls; let a = index;">
<div [formGroupName]="a">
<button class="w-5" mat-icon-button (click)="deleteTransaction(i,a)">
<i ofbicon>delete_forever</i>
</button>
<mat-form-field class="w-15">
<mat-select formControlName="typeId">
<mat-option *ngFor="let type of types" [value]="type.value" >
{{type.display}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="w-20">
<input matInput placeholder="Check Number" type="text" formControlName="checkNumber" >
</mat-form-field>
<mat-form-field class="w-20">
<mat-select formControlName="fundId">
<mat-option *ngFor="let fund of funds" [value]="fund.value">
{{fund.display}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="w-20">
<input matInput placeholder="Amount" type="number" formControlName="amount" >
</mat-form-field>
<mat-form-field class="w-20">
<input matInput placeholder="Description" type="text" formControlName="description" >
</mat-form-field>
</div>
</div>
<button class="w-15" mat-stroked-button (click)="addTransaction(i)">Add Transaction</button>
<div class="ml-1 w-20 d-inline-block">
{{contributorName(i)}}
</div>
<div class="ml-1 w-20 d-inline-block">
General: {{contributorTotal(i, 1)}}
</div>
<div class="w-15 d-inline-block">
Missions: {{contributorTotal(i, 2)}}
</div>
<div class="w-15 d-inline-block">
Total: {{contributorTotal(i, 0)}}
</div>
</div>
</div>
<button mat-stroked-button class="m-1 w-15" (click)="addContributor()">Add Contributor</button>
<div class="ml-1 w-20 d-inline-block">
Combined
</div>
<div class="ml-1 w-20 d-inline-block">
General: {{combinedTotal(1)}}
</div>
<div class="w-15 d-inline-block">
Missions: {{combinedTotal(2)}}
</div>
<div class="w-15 d-inline-block">
Total: {{combinedTotal(0)}}
</div>
</form>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddTransactionPageComponent } from './add-transaction-page.component';
describe('AddTransactionPageComponent', () => {
let component: AddTransactionPageComponent;
let fixture: ComponentFixture<AddTransactionPageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AddTransactionPageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddTransactionPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,137 @@
import { Component, OnInit } from '@angular/core';
import { Transaction } from './transaction';
import { UserService } from 'src/app/services/user.service';
import { Contribution } from './contribution';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';
import { checkNumberValidator } from './check-number-validator';
@Component({
selector: 'app-add-transaction-page',
templateUrl: './add-transaction-page.component.html',
styleUrls: ['./add-transaction-page.component.css']
})
export class AddTransactionPageComponent implements OnInit {
errorMessages = [];
form: FormGroup;
addTransactionButtonDisabled: boolean = true;
addTransactionButtonText: string = "Submit";
types: {value:number,display:string}[] = [];
funds: {value:number,display:string}[] = [];
contributors: {value:number,display:string}[] = [];
filteredContributors: {value:number,display:string}[]
constructor(private userService: UserService, private formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
contributions: this.formBuilder.array([this.getContribution()])
});
}
ngOnInit() {
this.types = [{value:1, display: 'Cash'},{value:2, display: 'Check'}];
this.funds = [{value:1, display: 'General'},{value:2, display: 'Missions'}];
this.userService.getAll().subscribe(res => this.contributors = (<any>res).users);
}
getContribution(): FormGroup {
const date = new Date();
const dte = date.getFullYear() + "-" + (date.getMonth()<10?'0':'') + (date.getMonth() + 1) + "-" + (date.getDate()<10?'0':'') + date.getDate();
return this.formBuilder.group({
date: [dte, Validators.required],
contributorId: [0, Validators.required],
transactions: this.formBuilder.array([this.getTransaction(), this.getTransaction()])
});
}
getTransaction(): FormGroup {
return this.formBuilder.group({
amount: [null, Validators.required],
checkNumber: [''],
description: [''],
fundId: [1, [Validators.required, Validators.min(1), Validators.max(2)]],
taxYear: [(new Date()).getFullYear(), Validators.required],
typeId: [1, [Validators.required, Validators.min(1), Validators.max(2)]],
}, { validators: checkNumberValidator });
}
onContributorChange(event) {
console.log(event);
if (event && event.value) {
if (event.value === -1) {
// Open new user dialog
}
event = event.display;
}
const filterValue = event.toLowerCase();
this.filteredContributors = this.contributors.filter(option => option.display.toLowerCase().indexOf(filterValue) >= 0);
if (this.filteredContributors.length === 0) {
this.filteredContributors.push({value:-1, display:'Add New Contributor'});
}
}
contirbutorDisplayFn(contributor?: {value:number,display:string}): string | undefined {
return contributor ? contributor.display : undefined;
}
addTransaction(contributorPosition: number) {
const contributors = this.form.get('contributions') as FormArray;
const transactions = contributors.controls[contributorPosition].get('transactions') as FormArray;
transactions.push(this.getTransaction());
}
addContributor() {
const contributors = this.form.get('contributions') as FormArray;
contributors.push(this.getContribution());
}
deleteContributor(index: number) {
const contributors = this.form.get('contributions') as FormArray;
contributors.removeAt(index);
}
deleteTransaction(contributorIndex: number, transactionIndex: number) {
const contributors = this.form.get('contributions') as FormArray;
const transactions = contributors.controls[contributorIndex].get('transactions') as FormArray;
transactions.removeAt(transactionIndex);
}
contributorName(index: number) {
const contributors = this.form.get('contributions') as FormArray;
const contributor = contributors.controls[index].get('contributorId');
if (contributor && contributor.value.display) {
return contributor.value.display;
}
}
contributorTotal(index: number, fundId: number) {
const contributors = this.form.get('contributions') as FormArray;
const transactions = contributors.controls[index].get('transactions').value;
var sum = 0;
transactions.forEach(e => {
if (e.fundId === fundId || fundId === 0) {
sum += e.amount;
}
});
return sum;
}
combinedTotal(fundId: number) {
const contributors = this.form.get('contributions').value;
var sum = 0;
contributors.forEach(c => {
c.transactions.forEach(t => {
if (t.fundId === fundId || fundId === 0) {
sum += +t.amount;
}
});
});
return sum;
}
}

View File

@ -0,0 +1,11 @@
import { ValidatorFn, FormGroup, ValidationErrors } from '@angular/forms';
export const checkNumberValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const type = control.get('typeId').value;
const numb = control.get('checkNumber').value;
if (type === 2 && (!numb || numb === '')) {
return { 'checkNumber': true };
} else {
return null;
}
}

View File

@ -0,0 +1,7 @@
import { Transaction } from './transaction';
export class Contribution {
date: string;
contributorId: number;
transactions: Transaction[] = [];
}

View File

@ -0,0 +1,10 @@
export class Transaction {
date: string;
amount: number;
checkNumber: string;
contributorId: number;
description: string;
fundId: number;
taxYear: number;
typeId: number;
}

View File

@ -6,6 +6,9 @@
<div *ngIf="canAddUser">
<button (click)="addUserModal()">Add User</button>
</div>
<div>
<button [routerLink]="transactions/add">Add Transaction</button>
</div>
</div>
<div *ngIf="!user || !user.userName">
<button>Login</button>

View File

@ -5,6 +5,8 @@ import { UserRights } from 'src/app/constants/user-rights';
import { MatDialog } from '@angular/material';
import { AddUserPopupComponent } from '../popups/add-user-popup/add-user-popup.component';
import { take } from 'rxjs/operators';
import { TransactionService } from 'src/app/services/transaction.service';
import { LoginPopupComponent } from '../popups/login-popup/login-popup.component';
@Component({
selector: 'app-members-page',
@ -19,14 +21,14 @@ export class MembersPageComponent implements OnInit {
return this.user && this.user.canDo(UserRights.userAdd);
}
constructor(private loginService: LoginService, private matDialog: MatDialog) { }
constructor(private loginService: LoginService, private matDialog: MatDialog, private tservice: TransactionService) { }
ngOnInit() {
this.loginService.isLoggedIn(true).pipe(take(1)).subscribe(r => {
this.loading = false;
console.log(r);
if (r == false) {
this.addUserModal();
this.matDialog.open(LoginPopupComponent);
}
})
this.loginService.user.subscribe(u => this.user = u);
@ -35,5 +37,4 @@ export class MembersPageComponent implements OnInit {
addUserModal() {
let dialog = this.matDialog.open(AddUserPopupComponent);
}
}

View File

@ -12,7 +12,9 @@ export const SERMON_ADD_URL = environment.baseUrl + "/api2/sermons/a/";
export const SERMON_DELETE_URL = environment.baseUrl + "/api2/sermons/a/";
export const SERMON_UPDATE_URL = environment.baseUrl + "/api2/sermons/a/";
export const SERMON_DOWNLOAD_URL = environment.baseUrl + "/api2/sermons/download/";
export const TRANSACTION_CREATE_URL = environment.baseUrl + "/api2/transactions/a/";
export const USER_CREATE_URL = environment.baseUrl + "/api2/users/a/";
export const USER_GET_ALL_URL = environment.baseUrl + "/api2/users/a";
export const LOGIN_URL = environment.baseUrl + '/api2/login';
export const LOGIN_VALIDATE_TOKEN = '';
export const EMAIL_URL = environment.baseUrl + "/api2/email";

View File

@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, Subject, throwError, of } from 'rxjs';
import { catchError, map, tap, first } from 'rxjs/operators';
import { TRANSACTION_CREATE_URL } from '../constants/urls';
@Injectable()
export class TransactionService {
private options: {};
constructor(private httpClient: HttpClient){
this.options = {
withCredentials: true
};
}
create(date: Date, typeId: number, fundId: number, contributorId: number, description: string, amount: number, taxYear: number) {
const body = {
date: date,
typeId: typeId,
fundId: fundId,
contributorId: contributorId,
description: description,
amount: amount,
taxYear: taxYear
};
console.log(body);
return this.httpClient.post(TRANSACTION_CREATE_URL, body, this.options)
.pipe(tap(res => console.log(res)), catchError(this.handleError));
}
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
console.error(error);
}
// return an observable with a user-facing error message
return throwError((error && error.error && error.error.message) ? error.error.message :
'Something bad happened; please try again later.');
};
}

View File

@ -4,7 +4,7 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, Subject, throwError, of } from 'rxjs';
import { catchError, map, tap, first } from 'rxjs/operators';
import { LOGIN_URL, USER_CREATE_URL } from '../constants/urls';
import { LOGIN_URL, USER_CREATE_URL, USER_GET_ALL_URL } from '../constants/urls';
@Injectable()
export class UserService {
@ -17,6 +17,11 @@ export class UserService {
};
}
getAll(){
return this.httpClient.get(USER_GET_ALL_URL, this.options)
.pipe(tap(res => console.log(res)), catchError(this.handleError));
}
create(firstName: string, lastName: string, street: string, city: string, state: string, zip: string, country: string) {
const body = {
firstName: firstName,

View File

@ -100,6 +100,9 @@ function verifyToken(userId, tokenId, token, callback){
return;
}
user.rights = rights;
user.canDo = function(activity) {
return this.rights.includes(activity);
}.bind(user);
callback(null, isValid, user);
});

View File

@ -0,0 +1,41 @@
var connectionAsync = require("./connectionasync");
exports.getAll = async function() {
const queryResult = await connectionAsync.query('SELECT * FROM Transactions WHERE DeletedDate IS NULL;');
const result = [];
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
for(var i = 0 ; i < qqueryResult.rows.length; i++) {
const row = queryResult.rows[i];
const trans = {};
trans.id = row.Id;
trans.date = row.Date;
trans.typeId = row.TypeId;
trans.checkNumber = row.CheckNumber;
trans.contributorId = row.ContributorId;
trans.fundId = row.FundId;
trans.description = row.Description;
trans.amount = row.Amount;
trans.taxYear = row.TaxYear;
result.push(trans);
}
}
return result;
}
exports.add = async function(date, typeId, check, contributorId, fundId, description, amount, taxYear) {
const newTrans = {
Date: date,
TypeId: typeId,
CheckNumber: check,
ContributorId: contributorId,
FundId: fundId,
Description: description,
Amount: amount,
TaxYear: taxYear
};
const newTransResult = await connectionAsync.nonQuery('INSERT INTO Transactions Set ?', newTrans);
return newTransResult;
}

View File

@ -37,31 +37,46 @@ exports.getUser = function(userIdOrUserName, callback){
});
}
exports.getAll = async function() {
const queryResult = await connectionAsync.query('SELECT * FROM Users WHERE DeletedDate IS NULL;');
const users = [];
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
for(var i = 0; i < queryResult.rows.length; i++) {
users.push(rowToUser(queryResult.rows[i]));
}
}
return users;
}
exports.getUser2 = async function(email) {
const queryResult = await connectionAsync.query('SELECT * FROM Users WHERE UserName = ? AND DeletedDate IS NULL;', [email]);
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
const row = queryResult.rows[0];
const user = {};
user.id = row.Id;
user.userName = row.UserName;
user.password = row.Password;
user.firstName = row.FirstName;
user.lastName = row.LastName;
user.street = row.Street;
user.city = row.City;
user.state = row.State;
user.country = row.Country;
user.emailVerified = row.EmailVerified;
user.emailVerificationCode = row.EmailVerificationCode;
user.emailVerificationCodeDate = row.EmailVerificationCodeDate;
user.federated = row.Federated;
user.federationCode = row.federationCode;
return user;
return rowToUser(row);
} else {
return null;
}
}
function rowToUser(row) {
const user = {};
user.id = row.Id;
user.userName = row.UserName;
user.password = row.Password;
user.firstName = row.FirstName;
user.lastName = row.LastName;
user.street = row.Street;
user.city = row.City;
user.state = row.State;
user.country = row.Country;
user.emailVerified = row.EmailVerified;
user.emailVerificationCode = row.EmailVerificationCode;
user.emailVerificationCodeDate = row.EmailVerificationCodeDate;
user.federated = row.Federated;
user.federationCode = row.federationCode;
return user;
}
exports.createUser = async function(firstName, lastName, street, city, state, zip, country) {
// Check to see if email already exists
const tempUserName = firstName + " " + lastName;

View File

@ -9,6 +9,7 @@ const fs = require('fs');
router.use("/users/a", require("./require-auth"));
router.use("/sermons/a", require("./require-auth"));
router.use("/events/a", require("./require-auth"));
router.use("/transactions/a", require("./require-auth"));
// routes
router.use("/", require("./main"));
@ -17,6 +18,7 @@ router.use("/sermons", require("./sermons"));
router.use("/events", require("./events"));
router.use("/login", require("./login"));
router.use("/email", require("./email"));
router.use("/transactions", require("./transactions"));
router.use('/share',require('./share'));

View File

@ -63,6 +63,7 @@ router.use(upload.single('file'),function(req,res,next){
req.body.finalPath = finalStorage + req.file.filename;
req.body.tmpPath = req.file.destination + req.file.filename;
}
res.locals.user = user;
next();
});
});

View File

@ -0,0 +1,30 @@
var express = require('express');
var router = express.Router();
var dbTransactions = require("../../database/transactions");
router.post("/a/",async function(req,res) {
if (!res.locals.user || !res.locals.user.canDo || !res.locals.user.canDo('transactions_add')) {
res.status(401).json({"status":401,"message":"you are not authorized to add a transaction"});
return;
}
const result = await dbTransactions.add(req.body.date, req.body.typeId, req.body.check, req.body.contributorId, req.body.fundId, req.body.descriptions, req.body.amount, req.body.taxYear);
console.log(result);
console.log("new user");
console.log(req.body);
console.log(res.locals.user);
console.log(res.locals.user.canDo('sermons_add'));
console.log(res.locals.user.canDo('sermons_addd'));
return;
if (!req.body.lastName){
res.status(400).json({"status":400,"message":"last name is required fields in the body"});
return;
}
try {
var newUser = await dbUsers.createUser(req.body.firstName, req.body.lastName, req.body.street, req.body.city, req.body.state, req.body.zip, req.body.country);
res.status(201).json({"status":201,"message":"user created","user":newUser});
} catch (ex) {
res.status(500).json({"status":500,"message":ex});
}
});
module.exports = router;

View File

@ -3,6 +3,23 @@ var router = express.Router();
var dbUsers = require("../../database/users");
router.get("/a/", async function(req, res) {
if (!res.locals.user || !res.locals.user.canDo || !res.locals.user.canDo('user_add')) {
res.status(401).json({"status":401,"message":"you are not authorized to add a transaction"});
return;
}
const users = await dbUsers.getAll();
const usersRes = users.map(x => {
return {
value: x.id,
display: `${x.lastName} ${x.firstName}`
};
});
res.status(201).json({"status":201,"message":"all users","users":usersRes});
});
router.post("/a/",async function(req,res) {
console.log("new user");
console.log(req.body);