transactions

This commit is contained in:
2024-12-01 15:00:08 +01:00
parent 313821ef7b
commit 22e52ea46c
12 changed files with 299 additions and 50 deletions
@@ -32,4 +32,6 @@ public interface UserAccountRepository extends UserAccountRepositoryWithBagRelat
@Query("select userAccount from UserAccount userAccount join fetch userAccount.users")
// List<UserAccount> findByUserLogin(String login);
List<UserAccount> findAllFetchAccounts();
List<UserAccount> findByName(String name);
}
@@ -3,4 +3,10 @@ package com.sasiedzi.event.service;
public enum Account {
Boisko,
Skarbiec,
Counter {
@Override
public String toString() {
return "Subkonto Tomka";
}
},
}
@@ -1,17 +1,16 @@
package com.sasiedzi.event.service;
import com.sasiedzi.event.domain.Event;
import com.sasiedzi.event.domain.Transaction;
import com.sasiedzi.event.domain.TransactionItem;
import com.sasiedzi.event.domain.UserAccount;
import com.sasiedzi.event.domain.enumeration.TransactionType;
import com.sasiedzi.event.repository.TransactionItemRepository;
import com.sasiedzi.event.repository.TransactionRepository;
import com.sasiedzi.event.repository.UserAccountRepository;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -27,52 +26,61 @@ public class TransactionService {
@Autowired
UserAccountRepository userAccountRepository;
@Autowired
private TransactionItemRepository transactionItemRepository;
@Autowired
private EventService eventService;
public Transaction save(Transaction transaction) {
Set<TransactionItem> items = transaction.getTransactionItems();
Set<TransactionItem> vaults = items
String beneficiaryAccountName = transaction.getBeneficiary() == null
? Account.Skarbiec.toString()
: transaction.getBeneficiary().getName();
Set<TransactionItem> beneficiaryItems = items
.stream()
.filter(item -> Account.Skarbiec.name().equals(item.getUserAccount().getName()))
.filter(item -> beneficiaryAccountName.equals(item.getUserAccount().getName()))
.collect(Collectors.toSet());
Set<TransactionItem> nonVaults = items
Set<TransactionItem> nonBeneficiaryItems = items
.stream()
.filter(item -> !Account.Skarbiec.name().equals(item.getUserAccount().getName()))
.filter(item -> !beneficiaryAccountName.equals(item.getUserAccount().getName()))
.collect(Collectors.toSet());
BigDecimal vaultValue = nonVaults
BigDecimal beneficiaryValue = nonBeneficiaryItems
.stream()
.map(TransactionItem::getAmount)
.map(BigDecimal::negate)
.reduce(BigDecimal.ZERO, BigDecimal::add);
boolean vaultIsNeeded = vaultValue.compareTo(BigDecimal.ZERO) != 0;
boolean beneficiaryItemIsNeeded = beneficiaryValue.compareTo(BigDecimal.ZERO) != 0;
HashSet<TransactionItem> itemsToBeRemoved = new HashSet<>();
if (vaultIsNeeded) {
if (vaults.isEmpty()) {
if (beneficiaryItemIsNeeded) {
if (beneficiaryItems.isEmpty()) {
TransactionItem transactionItem = new TransactionItem();
UserAccount vaultAccount = userAccountRepository
UserAccount beneficiaryAccount = userAccountRepository
.findAll()
.stream()
.filter(item -> Account.Skarbiec.name().equals(item.getName()))
.filter(item -> beneficiaryAccountName.equals(item.getName()))
.findFirst()
.orElse(null);
transactionItem.setUserAccount(vaultAccount);
transactionItem.setAmount(vaultValue);
transactionItem.setUserAccount(beneficiaryAccount);
transactionItem.setAmount(beneficiaryValue);
transactionItem.setTransaction(transaction);
transaction.getTransactionItems().add(transactionItem);
} else {
TransactionItem first = null;
for (TransactionItem item : vaults) {
for (TransactionItem item : beneficiaryItems) {
if (first == null) {
first = item;
} else {
itemsToBeRemoved.add(item);
}
}
first.setAmount(vaultValue);
first.setAmount(beneficiaryValue);
}
} else {
itemsToBeRemoved.addAll(vaults);
itemsToBeRemoved.addAll(beneficiaryItems);
}
itemsToBeRemoved.forEach(transactionItem -> {
@@ -100,4 +108,84 @@ public class TransactionService {
}
return newTransaction;
}
public Transaction createPayments(Long id) {
Transaction existing = transactionRepository.findOneWithEagerRelationships(id).orElseGet(Transaction::new);
Transaction newTransaction = new Transaction();
newTransaction.setType(TransactionType.INTERNALTRANSFER);
setBeneficiary(newTransaction);
newTransaction.setEvent(existing.getEvent());
calculateBalances(existing.getTransactionItems().stream().map(TransactionItem::getUserAccount).toList());
for (TransactionItem item : existing.getTransactionItems()) {
BigDecimal accountBalance = item.getUserAccount().getBalance();
if (accountBalance == null) {
accountBalance = BigDecimal.ZERO;
}
if (accountBalance.compareTo(BigDecimal.ZERO) < 0 && !Account.Skarbiec.toString().equals(item.getUserAccount().getName())) {
TransactionItem newItem = new TransactionItem();
newItem.setAmount(accountBalance.negate());
newItem.setTransaction(newTransaction);
newItem.setUserAccount(item.getUserAccount());
newItem.setEvent(item.getEvent());
newItem.setRegistration(item.getRegistration());
newItem.setComment(item.getComment());
newTransaction.getTransactionItems().add(newItem);
}
}
return newTransaction;
}
public Transaction createFieldPayment(Long eventId) {
Transaction newTransaction = new Transaction();
newTransaction.setType(TransactionType.FIELDPAYMENT);
setBeneficiary(newTransaction);
Event event = null;
if (eventId != null) {
event = eventService.findOne(eventId).orElse(null);
newTransaction.setEvent(event);
}
TransactionItem newItem = new TransactionItem();
newItem.setAmount(new BigDecimal("375"));
newItem.setTransaction(newTransaction);
newItem.setUserAccount(getAccount(Account.Counter));
newItem.setEvent(event);
newTransaction.getTransactionItems().add(newItem);
return newTransaction;
}
private void calculateBalances(List<UserAccount> userAccounts) {
Map<Long, List<TransactionItem>> itemsByUserAccountId = transactionItemRepository
.findAll()
.stream()
.collect(Collectors.groupingBy(item -> item.getUserAccount().getId()));
for (UserAccount userAccount : userAccounts) {
List<TransactionItem> itemsForAccount = itemsByUserAccountId.get(userAccount.getId());
BigDecimal balance = itemsForAccount.stream().map(TransactionItem::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
userAccount.setBalance(balance);
}
}
public Transaction get(Long id) {
Transaction transaction = transactionRepository.findOneWithEagerRelationships(id).orElse(null);
setBeneficiary(transaction);
return transaction;
}
private void setBeneficiary(Transaction transaction) {
switch (transaction.getType()) {
case INTERNALTRANSFER -> {
transaction.setBeneficiary(getAccount(Account.Counter));
}
case MATCH, PURCHASE -> {
transaction.setBeneficiary(getAccount(Account.Skarbiec));
}
case FIELDPAYMENT -> {
transaction.setBeneficiary(getAccount(Account.Boisko));
}
}
}
public UserAccount getAccount(Account account) {
return userAccountRepository.findByName(account.toString()).stream().min(Comparator.comparing(UserAccount::getId)).orElse(null);
}
}
@@ -271,19 +271,44 @@ public class TransactionResource {
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the transaction, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/{id}")
public ResponseEntity<Transaction> getTransaction(@PathVariable("id") Long id) {
public Transaction getTransaction(@PathVariable("id") Long id) {
LOG.debug("REST request to get Transaction : {}", id);
Optional<Transaction> transaction = transactionRepository.findOneWithEagerRelationships(id);
// if (transaction.isPresent()) {
// transaction.get().getTransactionItems().stream().sorted();
// transaction.get().getTransactionItems().sort(Comparator.comparing(item -> item.getUserAccount().getName()));
// }
return ResponseUtil.wrapOrNotFound(transaction);
return transactionService.get(id);
// Optional<Transaction> transaction = transactionRepository.findOneWithEagerRelationships(id);
// // if (transaction.isPresent()) {
// // transaction.get().getTransactionItems().stream().sorted();
// // transaction.get().getTransactionItems().sort(Comparator.comparing(item -> item.getUserAccount().getName()));
// // }
// return ResponseUtil.wrapOrNotFound(transaction);
}
@Autowired
TransactionService transactionService;
/**
* {@code GET /transactions/:id} : get the "id" transaction.
*
* @param id the id of the transaction to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the transaction, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/payments/{id}")
public Transaction createPayments(@PathVariable("id") Long id) {
LOG.debug("REST request to get Transaction : {}", id);
return transactionService.createPayments(id);
}
/**
* {@code GET /transactions/:id} : get the "id" transaction.
*
* @param id the id of the transaction to retrieve.
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the transaction, or with status {@code 404 (Not Found)}.
*/
@GetMapping("/field-payment/{id}")
public Transaction getTransactionPayments(@PathVariable("id") Long id) {
LOG.debug("REST request to get Transaction : {}", id);
return transactionService.createFieldPayment(id);
}
/**
* {@code GET /transactions/:id} : get the "id" transaction.
*
@@ -45,6 +45,31 @@
<font-awesome-icon icon="plus"></font-awesome-icon>&nbsp;<span>Dołącz do wydarzenia</span>
</button>
</router-link>
<!-- <router-link-->
<!-- :to="{ name: 'TransactionFieldPayment', params: { paymentForFieldByEventId: event.id } }"-->
<!-- custom-->
<!-- v-slot="{ navigate }"-->
<!-- v-if="hasAnyAuthority(['ROLE_ADMIN', 'ROLE_COUNTER'])"-->
<!-- >-->
<!-- <button @click="navigate" class="btn btn-primary">-->
<!-- <font-awesome-icon icon="plus"></font-awesome-icon>&nbsp;<span>Opłać boisko</span>-->
<!-- </button>-->
<!-- </router-link>-->
<router-link
v-if="event.id"
:to="{ name: 'TransactionFieldPayment', params: { paymentForFieldByEventId: event.id } }"
custom
v-slot="{ navigate }"
><!-- <router-link
v-if="transaction.id"
:to="{ name: 'TransactionCreateOpposite', params: { opposingTransactionId: transaction.id } }"
custom
v-slot="{ navigate }"
>-->
<button @click="navigate" class="btn btn-primary" v-if="hasAnyAuthority(['ROLE_ADMIN', 'ROLE_COUNTER'])">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>Dodaj płatność za boisko</span>
</button>
</router-link>
<button
class="btn btn-primary float-right"
v-if="isCurrentEvent && hasAnyAuthority(['ROLE_ADMIN', 'ROLE_COUNTER'])"
@@ -45,15 +45,32 @@
</button>
</router-link>
<router-link
v-if="transaction.id"
:to="{ name: 'TransactionPaymentsForTransaction', params: { paymentsForTransactionId: transaction.id } }"
custom
v-slot="{ navigate }"
><!-- <router-link
v-if="transaction.id"
:to="{ name: 'TransactionCreateOpposite', params: { opposingTransactionId: transaction.id } }"
custom
v-slot="{ navigate }"
>
>-->
<button @click="navigate" class="btn btn-primary" v-if="hasAnyAuthority(['ROLE_ADMIN', 'ROLE_COUNTER'])">
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>Create opposite transaction</span>
<font-awesome-icon icon="pencil-alt"></font-awesome-icon>&nbsp;<span>Dodaj wpłaty na konto</span>
</button>
</router-link>
<!-- :to="{ name: 'TransactionPaymentsForTransaction', params: { paymentsForTransactionId: transaction.id } }"-->
<!-- <router-link-->
<!-- :to="{ name: 'TransactionPaymentsForTransaction', params: { transactionId: transaction.id } }"-->
<!-- custom-->
<!-- v-slot="{ navigate }"-->
<!-- v-if="hasAnyAuthority(['ROLE_ADMIN', 'ROLE_COUNTER'])"-->
<!-- >-->
<!-- <button @click="navigate" class="btn btn-primary">-->
<!-- <font-awesome-icon icon="plus"></font-awesome-icon>&nbsp;<span>Dodaj wpłaty na konto</span>-->
<!-- </button>-->
<!-- </router-link>-->
<!-- <router-link :to="{ name: 'TransactionCreateOpposite', params: { opposingTransactionId: transaction.id } }">Create opposite transaction</router-link>-->
</div>
<div class="form-group">
@@ -42,16 +42,44 @@ export default defineComponent({
try {
if (dictUserAccounts.value.length == 0) {
const res2 = await userAccountService().retrieve();
console.log('got accounts' + res2);
dictUserAccounts.value = res2.data;
}
const res = await transactionService().find(transactionId, opposing);
transaction.value = res;
// console.log('len'+transaction.value.transactionItems?.length);
// console.log('len'+dictUserAccounts.value.length);
// console.log('id'+transaction.value.transactionItems[0].userAccount.id);
// console.log('id'+(dictUserAccounts.value.find(account => account.id === transaction.value.transactionItems[0].userAccount.id) || null));
// transaction.value.transactionItems[0].userAccount = dictUserAccounts.value.find(account => account.id === transaction.value.transactionItems[0].userAccount.id) || null;
transaction.value.transactionItems.forEach((item, index) => {
const matchingAccount = dictUserAccounts.value.find(account => account.id === item.userAccount.id);
transaction.value.transactionItems[index].userAccount = matchingAccount || null;
});
} catch (error) {
alertService.showHttpError(error.response);
}
};
const retrieveTransactionForPayments = async transactionId => {
try {
if (dictUserAccounts.value.length == 0) {
const res2 = await userAccountService().retrieve();
dictUserAccounts.value = res2.data;
}
const res = await transactionService().payments(transactionId);
transaction.value = res;
transaction.value.transactionItems.forEach((item, index) => {
const matchingAccount = dictUserAccounts.value.find(account => account.id === item.userAccount.id);
transaction.value.transactionItems[index].userAccount = matchingAccount || null;
});
} catch (error) {
alertService.showHttpError(error.response);
}
};
const retrieveTransactionForFieldPayment = async eventId => {
try {
if (dictUserAccounts.value.length == 0) {
const res2 = await userAccountService().retrieve();
dictUserAccounts.value = res2.data;
}
const res = await transactionService().fieldPayment(eventId);
transaction.value = res;
transaction.value.transactionItems.forEach((item, index) => {
const matchingAccount = dictUserAccounts.value.find(account => account.id === item.userAccount.id);
transaction.value.transactionItems[index].userAccount = matchingAccount || null;
@@ -76,14 +104,27 @@ export default defineComponent({
retrieveUserAccounts();
if (route.params?.transactionId) {
console.log('a1');
retrieveTransaction(route.params.transactionId, false);
}
if (route.params?.opposingTransactionId) {
console.log('a2');
console.log('getting opposite transaction for ' + route.params?.opposingTransactionId);
retrieveTransaction(route.params.opposingTransactionId, true);
}
if (route.params?.paymentsForTransactionId) {
console.log('a3');
retrieveTransactionForPayments(route.params.paymentsForTransactionId);
}
if (route.params?.paymentForFieldByEventId) {
console.log('a4');
retrieveTransactionForFieldPayment(route.params.paymentForFieldByEventId);
}
console.log('a5');
const initRelationships = () => {
eventService()
.retrieve()
@@ -76,7 +76,14 @@
</option>
</select>
</div>
<div class="form-group">
<label>Destination account</label>
<select v-model="transaction.beneficiary" class="form-control" v-if="dictUserAccounts.length">
<option v-for="account in dictUserAccounts" :key="account.id" :value="account">{{ account.name }}</option>
</select>
<label>Unsettled Balance:</label>
<input type="text" class="form-control" :value="calculateUnsettledBalance()" readonly />
</div>
<div class="form-group">
<button type="button" @click="addTransactionItem" class="btn btn-success">Add Transaction Item</button>
<div class="table-responsive">
@@ -84,7 +91,7 @@
<thead>
<tr>
<th scope="col"><span></span></th>
<th scope="col"><span>Locked</span></th>
<!-- <th scope="col"><span>Locked</span></th>-->
<th scope="col"><span>Account</span></th>
<th scope="col"><span>Amount</span></th>
<th scope="col"><span>Comment</span></th>
@@ -96,9 +103,9 @@
<td>
{{ item.id }}
</td>
<td>
<input type="checkbox" v-model="item.locked" class="form-check-input" />
</td>
<!-- <td>-->
<!-- <input type="checkbox" v-model="item.locked" class="form-check-input" />-->
<!-- </td>-->
<td>
<select v-model="item.userAccount" class="form-control" v-if="dictUserAccounts.length">
<option v-for="account in dictUserAccounts" :key="account.id" :value="account">{{ account.name }}</option>
@@ -118,10 +125,6 @@
</table>
</div>
</div>
<div class="form-group">
<label>Unsettled Balance:</label>
<input type="text" class="form-control" :value="calculateUnsettledBalance()" readonly />
</div>
</div>
<div>
<button type="button" id="cancel-save" data-cy="entityCreateCancelButton" class="btn btn-secondary" @click="previousState()">
@@ -22,6 +22,34 @@ export default class TransactionService {
});
}
public fieldPayment(eventId: number): Promise<ITransaction> {
return new Promise<ITransaction>((resolve, reject) => {
let url = `${baseApiUrl}/field-payment/${eventId}`;
axios
.get(url)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
public payments(transactionId: number): Promise<ITransaction> {
return new Promise<ITransaction>((resolve, reject) => {
let url = `${baseApiUrl}/payments/${transactionId}`;
axios
.get(url)
.then(res => {
resolve(res.data);
})
.catch(err => {
reject(err);
});
});
}
public retrieve(): Promise<any> {
return new Promise<any>((resolve, reject) => {
axios
@@ -6,12 +6,7 @@
<button class="btn btn-info mr-2" @click="handleSyncList" :disabled="isFetching">
<font-awesome-icon icon="sync" :spin="isFetching"></font-awesome-icon> <span>Refresh list</span>
</button>
<router-link
:to="{ name: 'TransactionCreate' }"
custom
v-slot="{ navigate }"
v-if="hasAnyAuthority(['ROLE_ADMIN', 'ROLE_COUNTER'])"
>
<router-link :to="{ name: 'TransactionCreate' }" custom v-slot="{ navigate }" v-if="hasAnyAuthority(['ROLE_ADMIN'])">
<button
@click="navigate"
id="jh-create-entity"
+18
View File
@@ -126,6 +126,24 @@ export default {
component: TransactionUpdate,
meta: { authorities: [Authority.ADMIN, Authority.COUNTER] },
},
{
path: 'transaction/field-payment/:paymentForFieldByEventId/new',
name: 'TransactionFieldPayment',
component: TransactionUpdate,
meta: { authorities: [Authority.ADMIN, Authority.COUNTER] },
},
{
path: 'transaction/payments/:paymentsForTransactionId/new',
name: 'TransactionPaymentsForTransaction',
component: TransactionUpdate,
meta: { authorities: [Authority.ADMIN, Authority.COUNTER] },
},
{
path: 'transaction/opposing/:opposingTransactionId/new',
name: 'TransactionCreateOpposite',
component: TransactionUpdate,
meta: { authorities: [Authority.ADMIN, Authority.COUNTER] },
},
{
path: 'transaction/:transactionId/edit',
name: 'TransactionEdit',
@@ -10,6 +10,7 @@ export interface ITransaction {
event?: IEvent | null;
transactionItems?: ITransactionItem[] | [];
items?: ITransactionItem[] | [];
beneficiary?: IUserAccount | null;
}
export class Transaction implements ITransaction {
@@ -20,7 +21,7 @@ export class Transaction implements ITransaction {
public comment?: string | null,
public event?: IEvent | null,
public items?: ITransactionItem[] | [],
public transactionItems?: ITransactionItem[] | [],
public beneficiary?: IUserAccount | null,
) {}
}