mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into refactor_arithmetic_transactionlist
This commit is contained in:
commit
6d17786c59
@ -17,6 +17,6 @@ export class Balance {
|
||||
@Field(() => Decimal)
|
||||
decay: Decimal
|
||||
|
||||
@Field(() => String)
|
||||
decayDate: string
|
||||
@Field(() => Date)
|
||||
decayDate: Date
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { ObjectType, Field, Int } from 'type-graphql'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ObjectType()
|
||||
@ -31,6 +29,6 @@ export class Decay {
|
||||
@Field(() => Date, { nullable: true })
|
||||
end: Date | null
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@Field(() => Int, { nullable: true })
|
||||
duration: number | null
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { Decay } from './Decay'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
@ -25,7 +23,7 @@ export class Transaction {
|
||||
transaction.decay,
|
||||
transaction.decayStart,
|
||||
transaction.balanceDate,
|
||||
(transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000,
|
||||
Math.round((transaction.balanceDate.getTime() - transaction.decayStart.getTime()) / 1000),
|
||||
)
|
||||
}
|
||||
this.memo = transaction.memo
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import CONFIG from '../../config'
|
||||
import Decimal from 'decimal.js-light'
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { ObjectType, Field } from 'type-graphql'
|
||||
import { KlickTipp } from './KlickTipp'
|
||||
import { User as dbUser } from '@entity/User'
|
||||
|
||||
@ -307,13 +307,13 @@ export class AdminResolver {
|
||||
const receivedCallDate = new Date()
|
||||
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const lastUserTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
|
||||
const lastTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
|
||||
|
||||
let newBalance = new Decimal(0)
|
||||
if (lastUserTransaction) {
|
||||
if (lastTransaction) {
|
||||
newBalance = calculateDecay(
|
||||
lastUserTransaction.balance,
|
||||
lastUserTransaction.balanceDate,
|
||||
lastTransaction.balance,
|
||||
lastTransaction.balanceDate,
|
||||
receivedCallDate,
|
||||
).balance
|
||||
}
|
||||
@ -367,7 +367,7 @@ async function getUserCreations(ids: number[], includePending = true): Promise<C
|
||||
SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM
|
||||
(SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions
|
||||
WHERE user_id IN (${ids.toString()})
|
||||
AND transaction_type_id = ${TransactionTypeId.CREATION}
|
||||
AND type_id = ${TransactionTypeId.CREATION}
|
||||
AND creation_date >= ${dateFilter}
|
||||
${unionString}) AS result
|
||||
GROUP BY month, userId
|
||||
|
||||
@ -30,7 +30,8 @@ import { RIGHTS } from '../../auth/RIGHTS'
|
||||
import { User } from '../model/User'
|
||||
import { communityUser } from '../../util/communityUser'
|
||||
import { virtualDecayTransaction } from '../../util/virtualDecayTransaction'
|
||||
// import Decimal from '../scalar/Decimal'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { calculateDecay } from '../../util/decay'
|
||||
|
||||
@Resolver()
|
||||
export class TransactionResolver {
|
||||
@ -47,6 +48,7 @@ export class TransactionResolver {
|
||||
}: Paginated,
|
||||
@Ctx() context: any,
|
||||
): Promise<TransactionList> {
|
||||
const now = new Date()
|
||||
// find user
|
||||
const userRepository = getCustomRepository(UserRepository)
|
||||
// TODO: separate those usecases - this is a security issue
|
||||
@ -60,65 +62,6 @@ export class TransactionResolver {
|
||||
{ order: { balanceDate: 'DESC' } },
|
||||
)
|
||||
|
||||
if (!lastTransaction) {
|
||||
// TODO Have proper return type here
|
||||
throw new Error('User has no transactions')
|
||||
}
|
||||
|
||||
// find transactions
|
||||
const limit = currentPage === 1 && order === Order.DESC ? pageSize - 1 : pageSize
|
||||
const offset =
|
||||
currentPage === 1 ? 0 : (currentPage - 1) * pageSize - (order === Order.DESC ? 1 : 0)
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
|
||||
user.id,
|
||||
limit,
|
||||
offset,
|
||||
order,
|
||||
onlyCreations,
|
||||
)
|
||||
|
||||
// find involved users
|
||||
let involvedUserIds: number[] = []
|
||||
userTransactions.forEach((transaction: dbTransaction) => {
|
||||
involvedUserIds.push(transaction.userId)
|
||||
if (transaction.linkedUserId) {
|
||||
involvedUserIds.push(transaction.linkedUserId)
|
||||
}
|
||||
})
|
||||
// remove duplicates
|
||||
involvedUserIds = involvedUserIds.filter((value, index, self) => self.indexOf(value) === index)
|
||||
// We need to show the name for deleted users for old transactions
|
||||
const involvedDbUsers = await dbUser
|
||||
.createQueryBuilder()
|
||||
.withDeleted()
|
||||
.where('id IN (:...userIds)', { userIds: involvedUserIds })
|
||||
.getMany()
|
||||
const involvedUsers = involvedDbUsers.map((u) => new User(u))
|
||||
|
||||
const self = new User(user)
|
||||
const transactions: Transaction[] = []
|
||||
|
||||
// decay transaction
|
||||
if (currentPage === 1 && order === Order.DESC) {
|
||||
const now = new Date()
|
||||
transactions.push(
|
||||
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
|
||||
)
|
||||
}
|
||||
|
||||
// transactions
|
||||
for (let i = 0; i < userTransactions.length; i++) {
|
||||
const userTransaction = userTransactions[i]
|
||||
let linkedUser = null
|
||||
if (userTransaction.typeId === TransactionTypeId.CREATION) {
|
||||
linkedUser = communityUser
|
||||
} else {
|
||||
linkedUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
|
||||
}
|
||||
transactions.push(new Transaction(userTransaction, self, linkedUser))
|
||||
}
|
||||
|
||||
// get GDT
|
||||
let balanceGDT = null
|
||||
try {
|
||||
@ -134,9 +77,59 @@ export class TransactionResolver {
|
||||
console.log('Could not query GDT Server', err)
|
||||
}
|
||||
|
||||
if (!lastTransaction) {
|
||||
return new TransactionList(new Decimal(0), [], 0, balanceGDT)
|
||||
}
|
||||
|
||||
// find transactions
|
||||
// first page can contain 26 due to virtual decay transaction
|
||||
const offset = (currentPage - 1) * pageSize
|
||||
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
|
||||
user.id,
|
||||
pageSize,
|
||||
offset,
|
||||
order,
|
||||
onlyCreations,
|
||||
)
|
||||
|
||||
// find involved users; I am involved
|
||||
const involvedUserIds: number[] = [user.id]
|
||||
userTransactions.forEach((transaction: dbTransaction) => {
|
||||
if (transaction.linkedUserId && !involvedUserIds.includes(transaction.linkedUserId)) {
|
||||
involvedUserIds.push(transaction.linkedUserId)
|
||||
}
|
||||
})
|
||||
// We need to show the name for deleted users for old transactions
|
||||
const involvedDbUsers = await dbUser
|
||||
.createQueryBuilder()
|
||||
.withDeleted()
|
||||
.where('id IN (:...userIds)', { userIds: involvedUserIds })
|
||||
.getMany()
|
||||
const involvedUsers = involvedDbUsers.map((u) => new User(u))
|
||||
|
||||
const self = new User(user)
|
||||
const transactions: Transaction[] = []
|
||||
|
||||
// decay transaction
|
||||
if (currentPage === 1 && order === Order.DESC) {
|
||||
transactions.push(
|
||||
virtualDecayTransaction(lastTransaction.balance, lastTransaction.balanceDate, now, self),
|
||||
)
|
||||
}
|
||||
|
||||
// transactions
|
||||
userTransactions.forEach((userTransaction) => {
|
||||
const linkedUser =
|
||||
userTransaction.typeId === TransactionTypeId.CREATION
|
||||
? communityUser
|
||||
: involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
|
||||
transactions.push(new Transaction(userTransaction, self, linkedUser))
|
||||
})
|
||||
|
||||
// Construct Result
|
||||
return new TransactionList(
|
||||
lastTransaction.balance,
|
||||
calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now).balance,
|
||||
transactions,
|
||||
userTransactionsCount,
|
||||
balanceGDT,
|
||||
@ -184,7 +177,7 @@ export class TransactionResolver {
|
||||
transactionSend.memo = memo
|
||||
transactionSend.userId = senderUser.id
|
||||
transactionSend.linkedUserId = recipientUser.id
|
||||
transactionSend.amount = amount
|
||||
transactionSend.amount = amount.mul(-1)
|
||||
transactionSend.balance = sendBalance.balance
|
||||
transactionSend.balanceDate = receivedCallDate
|
||||
transactionSend.decay = sendBalance.decay.decay
|
||||
|
||||
@ -7,36 +7,35 @@ describe('utils/decay', () => {
|
||||
it('has base 0.99999997802044727', () => {
|
||||
const amount = new Decimal(1.0)
|
||||
const seconds = 1
|
||||
expect(decayFormula(amount, seconds)).toBe(0.99999997802044727)
|
||||
})
|
||||
// Not sure if the following skiped tests make sence!?
|
||||
it('has negative decay?', async () => {
|
||||
const amount = new Decimal(1.0)
|
||||
const seconds = 1
|
||||
expect(decayFormula(amount, seconds)).toBe(-0.99999997802044727)
|
||||
// TODO: toString() was required, we could not compare two decimals
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('0.999999978035040489732012')
|
||||
})
|
||||
it('has correct backward calculation', async () => {
|
||||
const amount = new Decimal(1.0)
|
||||
const seconds = -1
|
||||
expect(decayFormula(amount, seconds)).toBe(1.0000000219795533)
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('1.000000021964959992727444')
|
||||
})
|
||||
// not possible, nodejs hasn't enough accuracy
|
||||
it('has correct forward calculation', async () => {
|
||||
const amount = new Decimal(1.0).div(0.99999997802044727)
|
||||
// we get pretty close, but not exact here, skipping
|
||||
it.skip('has correct forward calculation', async () => {
|
||||
const amount = new Decimal(1.0).div(
|
||||
new Decimal('0.99999997803504048973201202316767079413460520837376'),
|
||||
)
|
||||
const seconds = 1
|
||||
expect(decayFormula(amount, seconds)).toBe(1.0)
|
||||
expect(decayFormula(amount, seconds).toString()).toBe('1.0')
|
||||
})
|
||||
})
|
||||
it.skip('has base 0.99999997802044727', async () => {
|
||||
it('has base 0.99999997802044727', async () => {
|
||||
const now = new Date()
|
||||
now.setSeconds(1)
|
||||
const oneSecondAgo = new Date(now.getTime())
|
||||
oneSecondAgo.setSeconds(0)
|
||||
expect(calculateDecay(new Decimal(1.0), oneSecondAgo, now)).toBe(0.99999997802044727)
|
||||
expect(calculateDecay(new Decimal(1.0), oneSecondAgo, now).balance.toString()).toBe(
|
||||
'0.999999978035040489732012',
|
||||
)
|
||||
})
|
||||
|
||||
it('returns input amount when from and to is the same', async () => {
|
||||
const now = new Date()
|
||||
expect(calculateDecay(new Decimal(100.0), now, now).balance).toBe(100.0)
|
||||
expect(calculateDecay(new Decimal(100.0), now, now).balance.toString()).toBe('100')
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user