Compare commits

...

10 Commits

34 changed files with 1647 additions and 498 deletions

View File

@ -13,6 +13,10 @@ Reference for adding JWT authentication is [here](https://jasonwatmore.com/net-7
### User Interface
Use [layoutit.com](https://www.layoutit.com/build).
[React Select](https://react-select.com/home#fixed-options)
* View Income overview
* Projected Income - Based on statistical metrics (with controls for fine-tuning sample date range)
* Historical Income (with source breakdown)

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,12 @@
"merge": "^2.1.1",
"oidc-client": "^1.11.5",
"react": "^18.2.0",
"react-datepicker": "^4.25.0",
"react-dom": "^18.2.0",
"react-router-bootstrap": "^0.26.2",
"react-router-dom": "^6.11.0",
"react-scripts": "^5.0.1",
"react-select": "^5.8.0",
"reactstrap": "^9.1.9",
"rimraf": "^5.0.0",
"web-vitals": "^3.3.1",

View File

@ -5,13 +5,17 @@ import CreateAccount from "./components/accounts/CreateAccount";
import DeleteAccount from "./components/accounts/DeleteAccount";
import UpdateAccount from "./components/accounts/UpdateAccount";
import Accounts from "./components/accounts/Accounts";
import { Login } from "./components/auth/Login";
import Login from "./components/auth/Login";
import { Logout } from "./components/auth/Logout";
import Register from "./components/auth/Register";
import CreateUser from "./components/users/CreateUser";
import DeleteUser from "./components/users/DeleteUser";
import UpdateUser from "./components/users/UpdateUser";
import Users from "./components/users/Users";
import { Routes } from "./components/services/Routes";
import Transactions from "./components/transactions/Transactions";
import CreateTransaction from "./components/transactions/CreateTransaction";
const AppRoutes = [
{
@ -19,59 +23,75 @@ const AppRoutes = [
element: <Home />
},
{
path: '/home',
path: Routes.HOME,
element: <Home />
},
{
path: '/counter',
path: Routes.COUNTER,
element: <Counter />
},
{
path: '/fetch-data',
path: Routes.FETCH_DATA,
element: <FetchData />
},
{
path: '/login',
path: Routes.LOGIN,
element: <Login />
},
{
path: '/logout',
path: Routes.LOGOUT,
element: <Logout />
},
{
path: '/register',
path: Routes.REGISTER,
element: <Register />
},
{
path: '/admin/users',
path: Routes.USERS,
element: <Users />
},
{
path: '/admin/user/create',
path: Routes.USER_CREATE,
element: <CreateUser />
},
{
path: '/admin/user/edit',
path: Routes.USER_EDIT,
element: <UpdateUser />
},
{
path: '/admin/user/delete',
path: Routes.USER_DELETE,
element: <DeleteUser />
},
{
path: '/admin/accounts',
path: Routes.ACCOUNTS,
element: <Accounts />
},
{
path: '/admin/account/create',
path: Routes.ACCOUNT_CREATE,
element: <CreateAccount />
},
{
path: '/admin/account/edit',
path: Routes.ACCOUNT_EDIT,
element: <UpdateAccount />
},
{
path: '/admin/account/delete',
path: Routes.ACCOUNT_DELETE,
element: <DeleteAccount />
},
{
path: Routes.TRANSACTIONS,
element: <Transactions />
},
{
path: Routes.TRANSACTION_CREATE,
element: <CreateTransaction />
},
{
path: Routes.TRANSACTION_EDIT,
element: <UpdateAccount />
},
{
path: Routes.TRANSACTION_DELETE,
element: <DeleteAccount />
},
];

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { Collapse, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';
import { Routes } from "./services/Routes";
import './NavMenu.css';
export class NavMenu extends Component {
@ -32,30 +33,34 @@ export class NavMenu extends Component {
<NavItem>
<NavLink tag={Link} className="text-dark" to="/">Home</NavLink>
</NavItem>
{/*
<NavItem>
<NavLink tag={Link} className="text-dark" to="/counter">Counter</NavLink>
<NavLink tag={Link} className="text-dark" to={Routes.COUNTER}>Counter</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/fetch-data">Fetch data</NavLink>
</NavItem>
<NavLink tag={Link} className="text-dark" to={Routes.FETCH_DATA}>Fetch data</NavLink>
</NavItem>*/}
{sessionStorage.getItem('userName') == null &&
<NavItem>
<NavLink tag={Link} className="text-dark" to="/login">Login</NavLink>
<NavLink tag={Link} className="text-dark" to={Routes.LOGIN}>Login</NavLink>
</NavItem>
}
{sessionStorage.getItem('userName') != null &&
<>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/admin/users">Users</NavLink>
<NavLink tag={Link} className="text-dark" to={Routes.USERS}>Users</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to="/admin/accounts">Accounts</NavLink>
<NavLink tag={Link} className="text-dark" to={Routes.ACCOUNTS}>Accounts</NavLink>
</NavItem>
<NavItem>
<NavLink tag={Link} className="text-dark" to={Routes.TRANSACTIONS}>Transactions</NavLink>
</NavItem>
{/*<NavItem>
<NavLink tag={Link} className="text-dark" to="/login">{sessionStorage.getItem('firstName')}</NavLink>
<NavLink tag={Link} className="text-dark" to={Routes.LOGIN}>{sessionStorage.getItem('firstName')}</NavLink>
</NavItem>*/}
<NavItem>
<NavLink tag={Link} className="text-dark" to="/logout">Logout</NavLink>
<NavLink tag={Link} className="text-dark" to={Routes.LOGOUT}>Logout</NavLink>
</NavItem>
</>
}

View File

@ -1,11 +1,15 @@
import { getData } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { EndPoints, getData, putData } from "../services/AccessAPI";
import { RouterProvider, useNavigate } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { Routes } from "../services/Routes";
import RowButton from "../ui-elements/table/RowButton";
import DataFormatter, { RenderType } from "../ui-elements/table/DataFormatter";
export default function Accounts() {
useEffect(() => {
getAllAccountsData();
getAllUsersData();
}, [])
let navigate = useNavigate();
@ -15,27 +19,26 @@ export default function Accounts() {
loading: false
});
const [users, setUsers] = useState(null);
function onAccountCreate() {
navigate('/admin/account/create');
navigate(Routes.ACCOUNT_CREATE);
}
function onAccountEdit(id){
let path = "/admin/account/edit"
function onAccountEdit(id) {
let query = "?id=" + id
navigate(path + query);
navigate(Routes.ACCOUNT_EDIT + query);
}
function onAccountDelete(id){
let path = "/admin/account/delete"
function onAccountDelete(id) {
let query = "?id=" + id
navigate(path + query);
navigate(Routes.ACCOUNT_DELETE + query);
}
function getAllAccountsData() {
getData('Accounts/').then(
getData(EndPoints.ACCOUNTS).then(
(result) => {
if (result) {
console.log(result);
setState({
accounts: result,
loading: false
@ -45,33 +48,68 @@ export default function Accounts() {
);
}
function getAllUsersData() {
getData(EndPoints.USERS).then(
(result) => {
if (result) {
setUsers(result);
}
}
);
}
function refreshAccount(id) {
getData(EndPoints.ACCOUNTS_REFRESHBALANCE + "/" + id).then(
(result) => {
if (result) {
getAllAccountsData();
}
}
);
}
function findUserName(id) {
return users?.find(x => x.id)?.username;
}
function renderAllAccountsTable(accounts) {
return (
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Owner</th>
<th>Balance</th>
<th>External Account Number</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{
accounts.map(account => (
<tr key={account.id}>
<td>{account.name}</td>
<td>{account.owner}</td>
<td>{account.balance}</td>
<td>{account.externalAccountNumber}</td>
<td><button onClick={() => onAccountEdit(account.id)} className="btn btn-success">Edit</button> ||
<button onClick={() => onAccountDelete(account.id)} className="btn btn-danger">Delete</button></td>
</tr>
))
}
</tbody>
</table>
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<table className="table table-sm table-striped">
<thead>
<tr>
<th>Name</th>
<th>Owner</th>
<th>Balance</th>
<th>External Account Number</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{
accounts.map(account => (
<tr key={account.id}>
<td><DataFormatter data={account.name} /></td>
<td><DataFormatter data={findUserName(account.ownerId)} /></td>
<td><DataFormatter data={account.balance} renderType={RenderType.MoneyUnsigned} /></td>
<td><DataFormatter data={account.externalAccountNumber} /></td>
<td>
<RowButton className="btn btn-sm btn-outline-primary" text="Edit" onClick={() => onAccountEdit(account.id)} />
<RowButton className="btn btn-sm btn-outline-danger" text="Delete" onClick={() => onAccountDelete(account.id)} />
<RowButton className="btn btn-sm btn-outline-warning" text="Refresh" onClick={() => refreshAccount(account.id)} />
</td>
</tr>
))
}
</tbody>
</table>
</div>
</div>
</div>
);
}

View File

@ -1,42 +1,35 @@
import SessionManager from "../auth/SessionManager";
import { postData } from "../services/AccessAPI";
import { EndPoints, postData } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { useState } from 'react';
import { Routes } from "../services/Routes";
export default function CreateAccount() {
let navigate = useNavigate();
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [accountname, setAccountname] = useState("");
const [password, setPassword] = useState("");
const [confirmationPassword, setConfirmationPassword] = useState("");
const [role, setRole] = useState("");
const [name, setName] = useState("");
//const [owner, setOwner] = useState(0);
const [initBalance, setInitBalance] = useState("");
//const [currency, setCurrency] = useState(0);
const [extNum, setExtNum] = useState("");
const [loading, setLoading] = useState(true);
function onSubmit(e) {
e.preventDefault();
if (password !== confirmationPassword) {
alert("Password and confirm password are not same");
return;
}
let accountObj = {
firstName: firstName,
lastName: lastName,
email: email,
accountname: accountname,
password: password,
role: role,
name: name,
owner: SessionManager.getUserId(),
initialBalance: initBalance,
currency: 1,
externalAccountNumber: extNum,
}
postData('Accounts/register', accountObj).then((result) => {
postData(EndPoints.ACCOUNTS, accountObj).then((result) => {
setLoading(false);
let responseJson = result;
if (responseJson) {
navigate('/admin/accounts');
navigate(Routes.ACCOUNTS);
}
});
}
@ -45,54 +38,30 @@ export default function CreateAccount() {
e.preventDefault();
if(SessionManager.getToken()){
navigate('/admin/accounts');
navigate(Routes.ACCOUNTS);
}else{
navigate('/login');
navigate(Routes.LOGIN);
}
}
/*function onChange(e) {
//setState({ [e.target.name]: e.target.value });
}*/
return (
<div className="row">
<div className="col-md-4">
<h3>Create new account</h3>
<form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">First Name: </label>
<input className="form-control" type="text" name="firstName" value={firstName} onChange={(e) => setFirstName(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Last Name: </label>
<input className="form-control" type="text" name="lastName" value={lastName} onChange={(e) => setLastName(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Email: </label>
<input className="form-control" type="text" name="email" value={email} onChange={(e) => setEmail(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Account Name: </label>
<input className="form-control" type="text" name="accountname" value={accountname} onChange={(e) => setAccountname(e.target.value)}></input>
<input className="form-control" type="text" name="name" value={name} onChange={(e) => setName(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Role: </label>
<input className="form-control" type="text" name="roles" value={role} onChange={(e) => setRole(e.target.value)}></input>
<label className="control-label">Initial Balance: </label>
<input className="form-control" type="text" name="initBalance" value={initBalance} onChange={(e) => setInitBalance(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Password: </label>
<input className="form-control" type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Confirm Password: </label>
<input className="form-control" type="password" name="confirmationPassword" value={confirmationPassword} onChange={(e) => setConfirmationPassword(e.target.value)}></input>
<label className="control-label">External Account Number: </label>
<input className="form-control" type="text" name="extNum" value={extNum} onChange={(e) => setExtNum(e.target.value)}></input>
</div>
<div className="form-group">

View File

@ -1,27 +1,26 @@
import { useNavigate } from "react-router-dom";
import { deleteData, getData } from "../services/AccessAPI";
import { EndPoints, deleteData, getData } from "../services/AccessAPI";
import { useEffect } from "react";
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Routes } from "../services/Routes";
export default function DeleteAccount() {
const search = useLocation().search;
const id = new URLSearchParams(search).get('id')
useEffect(() => {
//const { id } = this.props.match.params;
getData('Accounts/' + id).then(
getData(EndPoints.ACCOUNTS + "/" + id).then(
(result) => {
console.log("Role for edit: ");
console.log(result);
if (result) {
setState({
firstName: result.firstName,
lastName: result.lastName,
accountname: result.accountname,
email: result.email,
roles: result.roles,
name: result.name,
lastActivity: result.lastActivity,
createdOn: result.createdOn,
balance: result.balance,
initialBalance: result.initialBalance,
currencyId: result.currencyId,
externalAccountNumber: result.externalAccountNumber,
loading: false
});
}
@ -32,70 +31,99 @@ export default function DeleteAccount() {
let navigate = useNavigate();
const [state, setState] = useState({
firstName: '',
lastName: '',
accountname: '',
email: '',
roles: [],
name: '',
lastActivity: null,
createdOn: null,
balance: 0,
initialBalance: 0,
currencyId: 0,
externalAccountNumber: '',
loading: true
});
function onCancel() {
navigate('/admin/accounts');
navigate(Routes.ACCOUNTS);
}
function onConfirmation(e) {
e.preventDefault();
deleteData('Accounts/' + id).then((result) => {
deleteData(EndPoints.ACCOUNTS + "/" + id).then((result) => {
let responseJson = result;
if (responseJson) {
navigate('/admin/accounts');
navigate(Routes.ACCOUNTS);
}
}
);
}
return (
<div>
<h2>::Delete account::</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Account Information</h4>
<dl className="row">
<dt className="col-sm-2">
First Name:
</dt>
<dd className="col-sm-10">
{state.firstName}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Last Name:
</dt>
<dd className="col-sm-10">
{state.lastName}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Account Name:
</dt>
<dd className="col-sm-10">
{state.accountname}
{state.name}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Email:
Last Activity:
</dt>
<dd className="col-sm-10">
{state.email}
{state.lastActivity}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Created On:
</dt>
<dd className="col-sm-10">
{state.createdOn}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Balance:
</dt>
<dd className="col-sm-10">
{state.balance}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Initial Balance:
</dt>
<dd className="col-sm-10">
{state.initialBalance}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
Currency ID:
</dt>
<dd className="col-sm-10">
{state.currencyId}
</dd>
</dl>
<dl className="row">
<dt className="col-sm-2">
External Account Number:
</dt>
<dd className="col-sm-10">
{state.externalAccountNumber}
</dd>
</dl>

View File

@ -1,35 +1,30 @@
import { getData, putData } from "../services/AccessAPI";
import { EndPoints, getData, putData } from "../services/AccessAPI";
import { useNavigate, useLocation } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { Routes } from "../services/Routes";
export default function UpdateAccount() {
let navigate = useNavigate();
const search = useLocation().search;
const id = new URLSearchParams(search).get('id')
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
const [accountname, setAccountname] = useState("");
const [role, setRole] = useState("");
const [name, setName] = useState("");
//const [owner, setOwner] = useState(0);
const [initBalance, setInitBalance] = useState("");
//const [currency, setCurrency] = useState(0);
const [extNum, setExtNum] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
getData('Accounts/' + id).then(
getData(EndPoints.ACCOUNTS + "/" + id).then(
(result) => {
//let responseJson = result;
console.log("account for edit: ");
console.log(result);
console.log("Accountname: " + result.accountname)
if (result) {
setFirstName(result.firstName);
setLastName(result.lastName);
setEmail(result.email);
setAccountname(result.accountname);
setRole(result.roles);
setName(result.name);
//setOwner(result.ownerId);
setInitBalance(result.initialBalance);
//setCurrency(result.currencyId);
setExtNum(result.externalAccountNumber);
setLoading(false);
}
}
@ -43,32 +38,26 @@ export default function UpdateAccount() {
}
function onUpdateCancel() {
navigate('/admin/accounts');
navigate(Routes.ACCOUNTS);
}
function onSubmit(e){
function onSubmit(e) {
e.preventDefault();
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
let accountProfile = {
id: id,
Accountname: accountname,
FirstName: firstName,
LastName: lastName,
Email: email,
name: name,
//owner: ownerId,
initialBalance: initBalance,
//currency: currencyId,
externalAccountNumber: extNum,
}
putData('Accounts/' + id, accountProfile).then((result) => {
console.log("Put Request body: ");
console.log(accountProfile);
putData(EndPoints.ACCOUNTS + "/" + id, accountProfile).then((result) => {
let responseJson = result;
console.log("update response: ");
if(responseJson){
if (responseJson) {
console.log(responseJson);
navigate('/admin/accounts');
navigate(Routes.ACCOUNTS);
}
}
);
@ -79,28 +68,22 @@ export default function UpdateAccount() {
<div className="col-md-4">
<h3>Edit Account</h3>
<form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">First Name: </label>
<input className="form-control" type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} name="firstName"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Last Name: </label>
<input className="form-control" type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} name="lastName"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Account Name: </label>
<input className="form-control" type="text" value={accountname} onChange={(e) => setAccountname(e.target.value)} name="accountname"
<input className="form-control" type="text" value={name} onChange={(e) => setName(e.target.value)} name="name"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">Email: </label>
<input className="form-control" type="text" value={email} onChange={(e) => setEmail(e.target.value)} name="email"
onKeyDown={onKeyDown}></input>
<label className="control-label">Initial Balance: </label>
<input className="form-control" type="text" value={initBalance} onChange={(e) => setInitBalance(e.target.value)} name="initBalance"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">
<label className="control-label">External Account Number: </label>
<input className="form-control" type="text" value={extNum} onChange={(e) => setExtNum(e.target.value)} name="extNum"
onKeyDown={onKeyDown} ></input>
</div>
<div className="form-group">

View File

@ -1,9 +1,9 @@
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { postDataForLogin } from "../services/AccessAPI";
import { EndPoints, postDataForLogin } from "../services/AccessAPI";
import SessionManager from "./SessionManager";
import { Routes } from "../services/Routes";
export default function Login() {
@ -33,14 +33,14 @@ export default function Login() {
}
//console.log("login info: " + userInfo.password);
postDataForLogin('Users/authenticate', userObj).then((result) => {
postDataForLogin(EndPoints.USER_AUTHENTICATE, userObj).then((result) => {
if (result?.token) {
SessionManager.setUserSession(result.username, result.token, result.id, result.firstName, result.lastName)
if (SessionManager.getToken()) {
setLoading(false);
navigate('/home');
navigate(Routes.HOME);
}
}
@ -70,7 +70,7 @@ export default function Login() {
}
function registration() {
navigate('/register');
navigate(Routes.REGISTER);
}

View File

@ -1,5 +1,6 @@
import { Component } from "react";
import SessionManager from "./SessionManager";
import { Routes } from "../services/Routes";
export class Logout extends Component{
constructor(){
@ -12,7 +13,7 @@ export class Logout extends Component{
componentDidMount(){
console.log("component did mount for logout");
SessionManager.removeUserSession();
window.location.href = "/login";
window.location.href = Routes.LOGIN;
}
render(){

View File

@ -1,7 +1,8 @@
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { postData } from "../services/AccessAPI";
import { EndPoints, postData } from "../services/AccessAPI";
import { Routes } from "../services/Routes";
export default function Register() {
@ -44,9 +45,9 @@ export default function Register() {
role: role,
}
postData('Users/register', userObj).then((result) => {
postData(EndPoints.USER_REGISTER, userObj).then((result) => {
if (result) {
navigate('/login');
navigate(Routes.LOGIN);
} else {
let errors = '';
for (const key in result?.errors) {

View File

@ -6,6 +6,12 @@ const SessionManager = {
else return null;
},
getUserId() {
const id = sessionStorage.getItem('userId');
if (id) return id;
else return null;
},
setUserSession(userName, token, userId, firstName, lastName) {
sessionStorage.setItem('userName', userName);
sessionStorage.setItem('token', token);

View File

@ -1,6 +1,15 @@
import SessionManager from "../auth/SessionManager";
import { BASE_URL } from "./Settings";
export const EndPoints = {
USERS: "Users",
USER_AUTHENTICATE: "Users/authenticate",
USER_REGISTER: "Users/register",
ACCOUNTS: "Accounts",
ACCOUNTS_REFRESHBALANCE: "Accounts/RefreshBalance",
CURRENCYTYPES: "CurrencyTypes",
TRANSACTIONS: "Transactions",
}
export function getData(endPoint) {

View File

@ -0,0 +1,20 @@
export const Routes = {
HOME: "/home",
COUNTER: "/counter",
FETCH_DATA: "/fetch-data",
LOGIN: "/login",
LOGOUT: "/logout",
REGISTER: "/register",
USERS: "/admin/users",
USER_CREATE: "/admin/user/create",
USER_EDIT: "/admin/user/edit",
USER_DELETE: "/admin/user/delete",
ACCOUNTS: "/admin/accounts",
ACCOUNT_CREATE: "/admin/account/create",
ACCOUNT_EDIT: "/admin/account/edit",
ACCOUNT_DELETE: "/admin/account/delete",
TRANSACTIONS: "/admin/transactions",
TRANSACTION_CREATE: "/admin/transaction/create",
TRANSACTION_EDIT: "/admin/transaction/edit",
TRANSACTION_DELETE: "/admin/transaction/delete",
};

View File

@ -0,0 +1,167 @@
import SessionManager from "../auth/SessionManager";
import { EndPoints, postData, getData } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { useState } from 'react';
import { useEffect } from "react";
import { Routes } from "../services/Routes";
import Select from 'react-select'
import DatePicker from 'react-datepicker'
export default function CreateTransaction() {
useEffect(() => {
getAllAccountsData();
//getAllUsersData();
}, [])
let navigate = useNavigate();
//const [users, setUsers] = useState(null);
const [accounts, setAccounts] = useState([]);
const [description, setDescription] = useState("");
//const [owner, setOwner] = useState(0);
const [date, setDate] = useState("");
const [debitAccount, setDebitAccount] = useState(0);
const [creditAccount, setCreditAccount] = useState(0);
const [amount, setAmount] = useState(0);
const [notes, setNotes] = useState("");
//const [currencyType, setCurrencyType] = useState(0);
const [extNum, setExtNum] = useState("");
const [isPending, setIsPending] = useState(false);
const [loading, setLoading] = useState(true);
function onSubmit(e) {
e.preventDefault();
let transactionObj = {
owner: SessionManager.getUserId(),
date: date,//"2023-12-23T19:29:12.909Z"
externalId: extNum,
description: description,
debitAccount: debitAccount,
creditAccount: creditAccount,
amount: amount,
currencyType: 1,
notes: notes,
isPending: isPending
}
postData(EndPoints.TRANSACTIONS, transactionObj).then((result) => {
setLoading(false);
let responseJson = result;
if (responseJson) {
navigate(Routes.ACCOUNTS);
}
});
}
function getAllAccountsData() {
getData(EndPoints.ACCOUNTS).then(
(result) => {
if (result) {
setAccounts(result.map(a => {
return {
value: a.id,
label: a.name
}
}));
setLoading(false);
}
}
);
}
/*function getAllUsersData() {
getData(EndPoints.USERS).then(
(result) => {
if (result) {
setUsers(result);
setLoading(false);
}
}
);
}*/
function onClickBack(e){
e.preventDefault();
if(SessionManager.getToken()){
navigate(Routes.TRANSACTIONS);
}else{
navigate(Routes.LOGIN);
}
}
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
/*
{
"date": "2023-12-23T21:44:31.930Z",
"isPending": true
}
*/
return (
<div className="row">
<div className="col-md-4">
<h3>Create new transaction</h3>
<form onSubmit={onSubmit}>
<div className="form-group">
<label className="control-label">Description: </label>
<input className="form-control" type="text" name="description" value={description} onChange={(e) => setDescription(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Amount: </label>
<input className="form-control" type="text" name="amount" value={amount} onChange={(e) => setAmount(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Debit Account: </label>
<Select onChange={(e) => setDebitAccount(e.value)} name="debitAccount" options={accounts} isSearchable="true" isLoading={loading} />
</div>
<div className="form-group">
<label className="control-label">Credit Account: </label>
<Select onChange={(e) => setCreditAccount(e.value)} name="creditAccount" options={accounts} isSearchable="true" isLoading={loading} />
</div>
<div className="form-group">
<label className="control-label">Date: </label>
{/*<DatePicker selected={date} onChange={(date) => setDate(date)} />*/}
<input className="form-control" type="text" name="date" value={date} onChange={(e) => setDate(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">External Transaction Number: </label>
<input className="form-control" type="text" name="extNum" value={extNum} onChange={(e) => setExtNum(e.target.value)}></input>
</div>
<div className="form-group">
<label className="control-label">Notes: </label>
<input className="form-control" type="text" name="notes" value={notes} onChange={(e) => setNotes(e.target.value)}></input>
</div>
<div className="form-check">
<label>
<input
type="checkbox"
name="isPending"
checked={isPending}
onChange={(e) => setIsPending(!isPending)}
className="form-check-input"
/>
Is Pending
</label>
</div>
<div className="form-group">
<input type="submit" value="Create Transaction" className="btn btn-primary"></input> &nbsp; &nbsp;
<input type="button" value="Back" onClick={onClickBack} className="btn btn-primary"></input>
</div>
</form>
</div>
</div>
);
}

View File

@ -0,0 +1,131 @@
import { EndPoints, getData } from "../services/AccessAPI";
import { useNavigate, useLocation } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { Routes } from "../services/Routes";
import RowButton from "../ui-elements/table/RowButton";
import DataFormatter, { RenderType } from "../ui-elements/table/DataFormatter";
export default function Transactions() {
useEffect(() => {
getAllTransactionsData();
getAllAccountsData();
}, [])
let navigate = useNavigate();
//const search = useLocation().search;
//const accountId = new URLSearchParams(search).get('accountId')
const [loading, setLoading] = useState(true);
const [transactions, setTransactions] = useState([]);
const [accounts, setAccounts] = useState([]);
function onTransactionCreate() {
navigate(Routes.TRANSACTION_CREATE);
}
function onTransactionEdit(id){
let query = "?id=" + id
navigate(Routes.TRANSACTION_EDIT + query);
}
function onTransactionDelete(id){
let query = "?id=" + id
navigate(Routes.TRANSACTION_DELETE + query);
}
function getAllTransactionsData() {
//let url = EndPoints.TRANSACTIONS + '/';
/*if (accountId) {
url += '?accountId=' + accountId;
}*/
getData(EndPoints.TRANSACTIONS).then(
(result) => {
if (result) {
console.log()
setTransactions(result);
setLoading(false);
}
}
);
}
function getAllAccountsData() {
getData(EndPoints.ACCOUNTS).then(
(result) => {
if (result) {
setAccounts(result);
setLoading(false);
}
}
);
}
function findAccountName(id) {
let name = accounts?.find(x => x.id == id)?.name;
console.log("Id: " + id + ", Name: " + name);
return name;
}
function renderAllTransactionsTable(transactions) {
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<table className="table table-sm table-striped">
<thead>
<tr>
<th>Amount</th>
<th>Description</th>
<th>Date</th>
<th>External ID</th>
<th>Debit Acc</th>
<th>Credit Acc</th>
<th>Pending</th>
</tr>
</thead>
<tbody>
{
transactions.map(transaction => (
<tr key={transaction.id}>
<td><DataFormatter data={transaction.amount} renderType={RenderType.MoneyUnsigned}/></td>
<td><DataFormatter data={transaction.description} /></td>
<td><DataFormatter data={transaction.date} /></td>
<td><DataFormatter data={transaction.externalId} /></td>
<td><DataFormatter data={findAccountName(transaction.debitAccountId)} /></td>
<td><DataFormatter data={findAccountName(transaction.creditAccountId)} /></td>
<td><DataFormatter data={transaction.isPending ? "True" : "False"} /></td>
<td>
<RowButton className="btn btn-sm btn-outline-primary" text="Edit" onClick={() => onTransactionEdit(transaction.id)} />
<RowButton className="btn btn-sm btn-outline-danger" text="Delete" onClick={() => onTransactionDelete(transaction.id)} />
</td>
</tr>
))
}
</tbody>
</table>
</div>
</div>
</div>
);
}
let content = loading ? (
<p>
<em>Loading...</em>
</p>
) : (
renderAllTransactionsTable(transactions)
)
return (
<div>
<h3>List of Transactions</h3>
<button onClick={() => onTransactionCreate()} className="btn btn-primary">Create new transaction</button>
{content}
</div>
);
}

View File

@ -0,0 +1,55 @@
import React from 'react';
import '../main.css';
export const RenderType = {
MoneyUnsigned: "money-unsigned",
MoneySigned: "money-signed",
MoneyNoColor: "money-nocolor",
MoneyNothing: "money-nothing",
}
export default function DataFormatter({
data,
renderType,
currencySymbol = "$"
}) {
const isNotUndefinedOrNull = (object) => {
return !(object === undefined || object === null);
}
function filterData(data) {
if (renderType != undefined && renderType != null) {
let num = Number(data)
let moneySign = true;
switch (renderType)
{
case RenderType.MoneyUnsigned:
moneySign = false;
case RenderType.MoneySigned:
if (num >= 0) {
return <span className='text-success'>{ moneySign && "+"}{currencySymbol + (Math.round(num * 100) / 100).toFixed(2)}</span>
} else {
return <span className='text-danger'>{ moneySign && "-"}{currencySymbol + (Math.round(num * -100) / 100).toFixed(2)}</span>
}
break;
case RenderType.MoneyNoColor:
if (num >= 0) {
return moneySign && "+" + currencySymbol + (Math.round(num * 100) / 100).toFixed(2)
} else {
return moneySign && "-" + currencySymbol + (Math.round(num * -100) / 100).toFixed(2)
}
case RenderType.MoneyNothing:
return currencySymbol + (Math.round(Math.abs(num) * 100) / 100).toFixed(2)
}
}
return data
}
return (
filterData(data)
);
}

View File

@ -0,0 +1,10 @@
import React from 'react';
import '../main.css';
export default function RowButton({ text, onClick, hidden, className, disabled }) {
return (
<button disabled={disabled} className={className} onClick={onClick} hidden={hidden}>
{text}
</button>
);
}

View File

@ -0,0 +1,51 @@
import React from 'react';
import '../main.css';
//import VintageButton from '../../VintageButton';
//import { Badge } from 'reactstrap';
import { useNavigate } from "react-router-dom";
export default function Table({
headerRender,
bodyRender,
}) {
let navigate = useNavigate();
const routeChange = () =>{
//let path = `/services/gallery/project?id=` + projectId;
//navigate(path);
}
const isNotUndefinedOrNull = (object) => {
return !(object === undefined || object === null);
}
return (
<table>
<thead>
{ headerRender() }
</thead>
<tbody>
{ bodyRender() }
</tbody>
</table>
);
/*
<div className="outer-border floating-shadow vintage-gallery-card">
<div className="mid-border">
<div className="inner-border card">
<img src={imageUrl} className='card-img-top' style={{borderRadius: 0}} alt={imageCaption}/>
{(isNotUndefinedOrNull(title) || isNotUndefinedOrNull(dateText) || isNotUndefinedOrNull(body)
|| isNotUndefinedOrNull(priceText) || isNotUndefinedOrNull(buttonText)) &&
<div className='card-body'>
{isNotUndefinedOrNull(title) && <h5 className='card-title'>{title}</h5>}
{isNotUndefinedOrNull(dateText) && <p>{dateText}</p>}
{isNotUndefinedOrNull(body) && <p className='card-text'>{body}</p>}
{isNotUndefinedOrNull(priceText) && <p style={{fontSize: 20}}><Badge className='vintage-badge' color='dark'>{priceText}</Badge></p>}
{isNotUndefinedOrNull(buttonText) && <VintageButton text={buttonText} onClick={routeChange}></VintageButton>}
</div>}
</div>
</div>
</div>
*/
}

View File

@ -0,0 +1,22 @@
import React from 'react';
import '../main.css';
export default function Tr({
keyValue,
body
}) {
const isNotUndefinedOrNull = (object) => {
return !(object === undefined || object === null);
}
function filterData(data) {
return data
}
console.log("Key: " + keyValue);
return (
<tr key={keyValue}>{ body }</tr>
);
}

View File

@ -1,7 +1,8 @@
import SessionManager from "../auth/SessionManager";
import { postData } from "../services/AccessAPI";
import { EndPoints, postData } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { useState } from 'react';
import { Routes } from "../services/Routes";
export default function CreateUser() {
let navigate = useNavigate();
@ -32,11 +33,11 @@ export default function CreateUser() {
role: role,
}
postData('Users/register', userObj).then((result) => {
postData(EndPoints.USER_REGISTER, userObj).then((result) => {
setLoading(false);
let responseJson = result;
if (responseJson) {
navigate('/admin/users');
navigate(Routes.USERS);
}
});
}
@ -45,9 +46,9 @@ export default function CreateUser() {
e.preventDefault();
if(SessionManager.getToken()){
navigate('/admin/users');
navigate(Routes.USERS);
}else{
navigate('/login');
navigate(Routes.LOGIN);
}
}

View File

@ -1,8 +1,9 @@
import { useNavigate } from "react-router-dom";
import { deleteData, getData } from "../services/AccessAPI";
import { EndPoints, deleteData, getData } from "../services/AccessAPI";
import { useEffect } from "react";
import { useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Routes } from "../services/Routes";
export default function DeleteUser() {
const search = useLocation().search;
@ -11,7 +12,7 @@ export default function DeleteUser() {
useEffect(() => {
//const { id } = this.props.match.params;
getData('Users/' + id).then(
getData(EndPoints.USERS + "/" + id).then(
(result) => {
console.log("Role for edit: ");
console.log(result);
@ -41,16 +42,16 @@ export default function DeleteUser() {
});
function onCancel() {
navigate('/admin/users');
navigate(Routes.USERS);
}
function onConfirmation(e) {
e.preventDefault();
deleteData('Users/' + id).then((result) => {
deleteData(EndPoints.USERS + "/" + id).then((result) => {
let responseJson = result;
if (responseJson) {
navigate('/admin/users');
navigate(Routes.USERS);
}
}
);

View File

@ -1,7 +1,8 @@
import { getData, putData } from "../services/AccessAPI";
import { getData, putData, EndPoints } from "../services/AccessAPI";
import { useNavigate, useLocation } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { Routes } from "../services/Routes";
export default function UpdateUser() {
let navigate = useNavigate();
@ -18,7 +19,7 @@ export default function UpdateUser() {
useEffect(() => {
//const id = location.pathName.slice(location.pathName.lastIndexOf("/") , location.pathName.length);
getData('Users/' + id).then(
getData(EndPoints.USERS + "/" + id).then(
(result) => {
//let responseJson = result;
console.log("user for edit: ");
@ -43,7 +44,7 @@ export default function UpdateUser() {
}
function onUpdateCancel() {
navigate('/admin/users');
navigate(Routes.USERS);
}
function onSubmit(e){
@ -59,7 +60,7 @@ export default function UpdateUser() {
Email: email,
}
putData('Users/' + id, userProfile).then((result) => {
putData(EndPoints.USERS + "/" + id, userProfile).then((result) => {
console.log("Put Request body: ");
console.log(userProfile);
@ -68,7 +69,7 @@ export default function UpdateUser() {
if(responseJson){
console.log(responseJson);
navigate('/admin/users');
navigate(Routes.USERS);
}
}
);

View File

@ -1,7 +1,9 @@
import { getData } from "../services/AccessAPI";
import { getData, EndPoints } from "../services/AccessAPI";
import { useNavigate } from "react-router-dom";
import { useEffect } from "react";
import { useState } from 'react';
import { Routes } from "../services/Routes";
import RowButton from "../ui-elements/table/RowButton";
export default function Users() {
useEffect(() => {
@ -16,23 +18,21 @@ export default function Users() {
});
function onUserCreate() {
navigate('/admin/user/create');
navigate(Routes.USER_CREATE);
}
function onUserEdit(id){
let path = "/admin/user/edit"
let query = "?id=" + id
navigate(path + query);
navigate(Routes.USER_EDIT + query);
}
function onUserDelete(id){
let path = "/admin/user/delete"
let query = "?id=" + id
navigate(path + query);
navigate(Routes.USER_DELETE + query);
}
function getAllUsersData() {
getData('Users/').then(
getData(EndPoints.USERS).then(
(result) => {
if (result) {
console.log(result);
@ -75,12 +75,52 @@ export default function Users() {
);
}
function renderBootstrapTable(users) {
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<table className="table table-sm table-striped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>User Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{
users.map(user => (
<tr key={user.id}>
<td>{user.firstName}</td>
<td>{user.lastName}</td>
<td>{user.username}</td>
<td>{user.email}</td>
<td>
<RowButton className="btn btn-sm btn-outline-primary" text="Edit" onClick={() => onUserEdit(user.id)} />
<RowButton className="btn btn-sm btn-outline-danger" text="Delete" onClick={() => onUserDelete(user.id)} />
</td>
</tr>
))
}
</tbody>
</table>
</div>
</div>
</div>
);
}
let content = state.loading ? (
<p>
<em>Loading...</em>
</p>
) : (
renderAllUsersTable(state.users)
//renderAllUsersTable(state.users)
renderBootstrapTable(state.users)
)

View File

@ -67,4 +67,11 @@ public class AccountsController : ControllerBase
_accountService.Delete(id);
return Ok(new { message = "account deleted" });
}
[HttpGet("RefreshBalance/{id}")]
public IActionResult RefreshBalance(int id)
{
Account account = _accountService.RefreshAccountBalance(id);
return Ok(_mapper.Map<Account, AccountDTO>(account));
}
}

View File

@ -30,7 +30,7 @@ public class TransactionsController : ControllerBase
}
[HttpGet]
public IActionResult GetAll()
public IActionResult GetAll(int? accountId = null)
{
List<TransactionDto> transactionDtos = new List<TransactionDto>();

View File

@ -41,16 +41,10 @@ public class AutoMapperProfile : Profile
// AccountUpdateRequest -> Account
CreateMap<AccountUpdateRequest, Account>()
.ForAllMembers(x => x.Condition(
(src, dest, prop) =>
{
// ignore both null & empty string properties
if (prop == null) return false;
if (prop.GetType() == typeof(string) && string.IsNullOrEmpty((string)prop)) return false;
return true;
}
));
.ForMember(
dest => dest.OwnerId,
opt => opt.MapFrom(src => src.Owner)
);
// AccountCreateRequest -> Account
CreateMap<AccountCreateRequest, Account>()

View File

@ -3,6 +3,7 @@ namespace active_allocator.Helpers;
using Microsoft.EntityFrameworkCore;
using active_allocator.Entities;
using System.Diagnostics;
using System;
public class DataContext : DbContext
{
@ -19,6 +20,44 @@ public class DataContext : DbContext
options.UseInMemoryDatabase("TestDb");
}*/
public void AddAmountToAccount(int accoundId, decimal amount)
{
Account account = this.Accounts.Find(accoundId);
if (account != null)
{
account.Balance += amount;
this.Accounts.Update(account);
this.SaveChanges();
}
}
public void RecalculateAccountBalance(int id)
{
Account account = this.Accounts.Find(id);
decimal amount = account.InitialBalance;
List<Transaction> transactions = this.Transactions
.Include(t => t.DebitAccount)
.Include(t => t.CreditAccount)
.Where(t =>
(t.DebitAccount != null && t.DebitAccount.Id == id)
|| (t.CreditAccount != null && t.CreditAccount.Id == id))
.ToList();
foreach (Transaction t in transactions)
{
if (t.DebitAccount?.Id == id) {
amount -= t.Amount;
} else if (t.CreditAccount?.Id == id) {
amount += t.Amount;
}
}
account.Balance = amount;
this.Accounts.Update(account);
this.SaveChanges();
}
public DbSet<User> Users { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<Alloc> Allocs { get; set; }

View File

@ -5,11 +5,9 @@ using active_allocator.Entities;
public class AccountUpdateRequest
{
public string Name { get; set; }
public int OwnerId { get; set; }
public string InitialBalance { get; set; }
public int CurrencyId { get; set; }
public string ExternalAccountNumber { get; set; }
public ICollection<int> Allocs { get; set; }
public string Name { get; set; } = null;
public int? Owner { get; set; } = null;
public string InitialBalance { get; set; } = null;
public int? Currency { get; set; } = null;
public string ExternalAccountNumber { get; set; } = null;
}

View File

@ -10,14 +10,16 @@ using System.Collections.Generic;
using System.Linq;
using System;
using Internal;
using Microsoft.EntityFrameworkCore;
public interface IAccountService
{
IEnumerable<Account> GetAll();
Account GetById(int id);
Account GetById(int accountId);
void Create(AccountCreateRequest model);
void Update(int id, AccountUpdateRequest model);
void Delete(int id);
void Update(int accountId, AccountUpdateRequest model);
Account RefreshAccountBalance(int accountId);
void Delete(int accountId);
}
public class AccountService : IAccountService
@ -44,9 +46,9 @@ public class AccountService : IAccountService
return _context.Accounts;
}
public Account GetById(int id)
public Account GetById(int accountId)
{
return getAccount(id);
return getAccount(accountId);
}
public void Create(AccountCreateRequest model)
@ -85,23 +87,56 @@ public class AccountService : IAccountService
_context.SaveChanges();
}
public void Update(int id, AccountUpdateRequest model)
public void Update(int accountId, AccountUpdateRequest model)
{
var account = getAccount(id);
Account account = getAccount(accountId);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// Name
if (!string.IsNullOrWhiteSpace(model.Name))
account.Name = model.Name;
// Owner
if (model.Owner.HasValue)
{
account.OwnerId = model.Owner.Value;
account.Owner = _userService.GetById(model.Owner.Value);
}
// Initial Balance
if (!string.IsNullOrWhiteSpace(model.InitialBalance))
account.InitialBalance = Convert.ToDecimal(model.InitialBalance);
// CurrencyType
if (model.Currency.HasValue)
{
account.CurrencyId = model.Currency.Value;
account.Currency = _context.CurrencyTypes.Find(model.Currency.Value);
}
// External Account Number
if (!string.IsNullOrWhiteSpace(model.ExternalAccountNumber))
account.ExternalAccountNumber = model.ExternalAccountNumber;
// copy model to account and save
_mapper.Map(model, account);
//_mapper.Map(model, account);
_context.Accounts.Update(account);
_context.SaveChanges();
_context.RecalculateAccountBalance(accountId);
}
public void Delete(int id)
public Account RefreshAccountBalance(int accountId)
{
var account = getAccount(id);
_context.RecalculateAccountBalance(accountId);
return _context.Accounts.Find(accountId);
}
public void Delete(int accountId)
{
var account = getAccount(accountId);
_context.Accounts.Remove(account);
_context.SaveChanges();
}

View File

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System;
using Microsoft.EntityFrameworkCore;
using Internal;
public interface ITransactionService
{
@ -47,6 +48,20 @@ public class TransactionService : ITransactionService
return getTransaction(id);
}
private Account prepareAccount(int? accountId)
{
if (accountId == null || accountId.Value == 0)
{
accountId = 0;
}
Account account = _context.Accounts.Find(accountId.Value);
if (account == null)
throw new AppException("Could not find account with ID of '" + accountId.Value + "'.");
return account;
}
public void Create(TransactionCreate model)
{
Transaction transaction = new Transaction {
@ -55,17 +70,20 @@ public class TransactionService : ITransactionService
Date = model.Date,
CreatedOn = DateTime.UtcNow,
UpdatedOn = DateTime.UtcNow,
ExternalId = model.ExternalId,
DebitAccount = model.DebitAccount == null ? null : _context.Accounts.Find(model.DebitAccount.Value),
CreditAccount = model.CreditAccount == null ? null : _context.Accounts.Find(model.CreditAccount.Value),
ExternalId = string.IsNullOrWhiteSpace(model.ExternalId) ? "" : model.ExternalId,
DebitAccount = prepareAccount(model.DebitAccount),
CreditAccount = prepareAccount(model.CreditAccount),
Amount = Convert.ToDecimal(model.Amount),
CurrencyType = _context.CurrencyTypes.Find(model.CurrencyType),
Notes = model.Notes,
IsPending = model.IsPending,
};
if (transaction.DebitAccount.Id == transaction.CreditAccount.Id)
throw new AppException("The debit and credit accounts of a transaction cannot be the same.");
// Transaction Duplication Check
if (transaction.ExternalId != null && _context.Transactions.Any(x => x.ExternalId == transaction.ExternalId))
if (!string.IsNullOrWhiteSpace(transaction.ExternalId) && _context.Transactions.Any(x => x.ExternalId == transaction.ExternalId))
throw new AppException("Transaction with the external ID '" + transaction.ExternalId + "' already exists");
if (_context.Transactions.Any(x =>
@ -78,33 +96,22 @@ public class TransactionService : ITransactionService
{
throw new AppException("Transaction with the same fields already exists");
}
// map model to new account object
//var account = _mapper.Map<Account>(model);
_context.Transactions.Add(transaction);
_context.SaveChanges();
_context.AddAmountToAccount(transaction.DebitAccount.Id, -transaction.Amount);
_context.AddAmountToAccount(transaction.CreditAccount.Id, transaction.Amount);
}
/*public void Update(int id, AccountUpdateRequest model)
{
var account = getOuterTransaction(id);
// validate
if (model.Name != account.Name && _context.Accounts.Any(x => x.Name == model.Name))
throw new AppException("Account with the name '" + model.Name + "' already exists");
// copy model to account and save
_mapper.Map(model, account);
_context.Accounts.Update(account);
_context.SaveChanges();
}*/
public void Delete(int id)
{
var transaction = getTransaction(id);
_context.Transactions.Remove(transaction);
_context.SaveChanges();
_context.AddAmountToAccount(transaction.DebitAccount.Id, transaction.Amount);
_context.AddAmountToAccount(transaction.CreditAccount.Id, -transaction.Amount);
}
private Transaction getTransaction(int id)

View File

@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"PSQLConnection": "Server=localhost;Port=15432;Database=AADB;User Id=postgres;Password=nqA3UV3CliLLHpLL"
"PSQLConnection": "Server=localhost;Port=15432;Database=aadb;User Id=postgres;Password=nqA3UV3CliLLHpLL"
},
"Logging": {
"LogLevel": {