diff --git a/backend/package.json b/backend/package.json index 93a954d3f..95a41c616 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "tsc --build", "clean": "tsc --build --clean", - "start": "node build/index.js", + "start": "node build/src/index.js", "dev": "nodemon -w src --ext ts --exec ts-node src/index.ts", "lint": "eslint . --ext .js,.ts", "test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles" @@ -31,7 +31,6 @@ "jsonwebtoken": "^8.5.1", "lodash.clonedeep": "^4.5.0", "module-alias": "^2.2.2", - "moment": "^2.29.1", "mysql2": "^2.3.0", "nodemailer": "^6.6.5", "random-bigint": "^0.0.1", diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index e55e64edb..eab052bbc 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -12,7 +12,6 @@ import { UserRepository } from '../../typeorm/repository/User' import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs' import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs' import SearchUsersArgs from '../arg/SearchUsersArgs' -import moment from 'moment' import { Transaction } from '@entity/Transaction' import { UserTransaction } from '@entity/UserTransaction' import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction' @@ -48,6 +47,7 @@ export class AdminResolver { if (notActivated) { filterCriteria.push({ emailChecked: false }) } + if (isDeleted) { filterCriteria.push({ deletedAt: Not(IsNull()) }) } @@ -65,6 +65,8 @@ export class AdminResolver { pageSize, ) + const creations = await getUserCreations(users.map((u) => u.id)) + const adminUsers = await Promise.all( users.map(async (user) => { let emailConfirmationSend = '' @@ -152,7 +154,7 @@ export class AdminResolver { if (!user.emailChecked) { throw new Error('Creation could not be saved, Email is not activated') } - const creations = await getUserCreations(user.id) + const creations = await getUserCreation(user.id) const creationDateObj = new Date(creationDate) if (isCreationValid(creations, amount, creationDateObj)) { const adminPendingCreation = AdminPendingCreation.create() @@ -165,7 +167,7 @@ export class AdminResolver { await AdminPendingCreation.save(adminPendingCreation) } - return getUserCreations(user.id) + return getUserCreation(user.id) } @Authorized([RIGHTS.CREATE_PENDING_CREATION]) @@ -214,7 +216,7 @@ export class AdminResolver { } const creationDateObj = new Date(creationDate) - let creations = await getUserCreations(user.id) + let creations = await getUserCreation(user.id) if (pendingCreationToUpdate.date.getMonth() === creationDateObj.getMonth()) { creations = updateCreations(creations, pendingCreationToUpdate) } @@ -233,7 +235,8 @@ export class AdminResolver { result.memo = pendingCreationToUpdate.memo result.date = pendingCreationToUpdate.date result.moderator = pendingCreationToUpdate.moderator - result.creation = await getUserCreations(user.id) + + result.creation = await getUserCreation(user.id) return result } @@ -242,27 +245,27 @@ export class AdminResolver { @Query(() => [PendingCreation]) async getPendingCreations(): Promise { const pendingCreations = await AdminPendingCreation.find() + if (pendingCreations.length === 0) { + return [] + } - const pendingCreationsPromise = await Promise.all( - pendingCreations.map(async (pendingCreation) => { - const userRepository = getCustomRepository(UserRepository) - const user = await userRepository.findOneOrFail({ id: pendingCreation.userId }) + const userIds = pendingCreations.map((p) => p.userId) + const userCreations = await getUserCreations(userIds) + const users = await User.find({ id: In(userIds) }) - const parsedAmount = Number(parseInt(pendingCreation.amount.toString()) / 10000) - // pendingCreation.amount = parsedAmount - const newPendingCreation = { - ...pendingCreation, - amount: parsedAmount, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - creation: await getUserCreations(user.id), - } + return pendingCreations.map((pendingCreation) => { + const user = users.find((u) => u.id === pendingCreation.userId) + const creation = userCreations.find((c) => c.id === pendingCreation.userId) - return newPendingCreation - }), - ) - return pendingCreationsPromise.reverse() + return { + ...pendingCreation, + amount: Number(parseInt(pendingCreation.amount.toString()) / 10000), + firstName: user ? user.firstName : '', + lastName: user ? user.lastName : '', + email: user ? user.email : '', + creation: creation ? creation.creations : [1000, 1000, 1000], + } + }) } @Authorized([RIGHTS.DELETE_PENDING_CREATION]) @@ -282,6 +285,11 @@ export class AdminResolver { if (moderatorUser.id === pendingCreation.userId) throw new Error('Moderator can not confirm own pending creation') + const creations = await getUserCreation(pendingCreation.userId, false) + if (!isCreationValid(creations, Number(pendingCreation.amount) / 10000, pendingCreation.date)) { + throw new Error('Creation is not valid!!') + } + const receivedCallDate = new Date() let transaction = new Transaction() transaction.transactionTypeId = TransactionTypeId.CREATION @@ -335,129 +343,94 @@ export class AdminResolver { } } -async function getUserCreations(id: number): Promise { - const dateNextMonth = moment().add(1, 'month').format('YYYY-MM') + '-01' - const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') + '-01' - const beforeLastMonthNumber = moment().subtract(2, 'month').format('M') - const lastMonthNumber = moment().subtract(1, 'month').format('M') - const currentMonthNumber = moment().format('M') +interface CreationMap { + id: number + creations: number[] +} - const createdAmountsQuery = await Transaction.createQueryBuilder('transactions') - .select('MONTH(transactions.creation_date)', 'target_month') - .addSelect('SUM(transactions.amount)', 'sum') - .where('transactions.user_id = :id', { id }) - .andWhere('transactions.transaction_type_id = :type', { type: TransactionTypeId.CREATION }) - .andWhere({ - creationDate: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, { - date: dateBeforeLastMonth, - endDate: dateNextMonth, +async function getUserCreation(id: number, includePending = true): Promise { + const creations = await getUserCreations([id], includePending) + return creations[0] ? creations[0].creations : [1000, 1000, 1000] +} + +async function getUserCreations(ids: number[], includePending = true): Promise { + const months = getCreationMonths() + + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + + const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day' + + const unionString = includePending + ? ` + UNION + SELECT date AS date, amount AS amount, userId AS userId FROM admin_pending_creations + WHERE userId IN (${ids.toString()}) + AND date >= ${dateFilter}` + : '' + + const unionQuery = await queryRunner.manager.query(` + 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 creation_date >= ${dateFilter} + ${unionString}) AS result + GROUP BY month, userId + ORDER BY date DESC + `) + + await queryRunner.release() + + return ids.map((id) => { + return { + id, + creations: months.map((month) => { + const creation = unionQuery.find( + (raw: { month: string; id: string; creation: number[] }) => + parseInt(raw.month) === month && parseInt(raw.id) === id, + ) + return 1000 - (creation ? Number(creation.sum) / 10000 : 0) }), - }) - .groupBy('target_month') - .orderBy('target_month', 'ASC') - .getRawMany() - - const pendingAmountsQuery = await AdminPendingCreation.createQueryBuilder( - 'admin_pending_creations', - ) - .select('MONTH(admin_pending_creations.date)', 'target_month') - .addSelect('SUM(admin_pending_creations.amount)', 'sum') - .where('admin_pending_creations.userId = :id', { id }) - .andWhere({ - date: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, { - date: dateBeforeLastMonth, - endDate: dateNextMonth, - }), - }) - .groupBy('target_month') - .orderBy('target_month', 'ASC') - .getRawMany() - - const map = new Map() - if (Array.isArray(createdAmountsQuery) && createdAmountsQuery.length > 0) { - createdAmountsQuery.forEach((createdAmount) => { - if (!map.has(createdAmount.target_month)) { - map.set(createdAmount.target_month, createdAmount.sum) - } else { - const store = map.get(createdAmount.target_month) - map.set(createdAmount.target_month, Number(store) + Number(createdAmount.sum)) - } - }) - } - - if (Array.isArray(pendingAmountsQuery) && pendingAmountsQuery.length > 0) { - pendingAmountsQuery.forEach((pendingAmount) => { - if (!map.has(pendingAmount.target_month)) { - map.set(pendingAmount.target_month, pendingAmount.sum) - } else { - const store = map.get(pendingAmount.target_month) - map.set(pendingAmount.target_month, Number(store) + Number(pendingAmount.sum)) - } - }) - } - const usedCreationBeforeLastMonth = map.get(Number(beforeLastMonthNumber)) - ? Number(map.get(Number(beforeLastMonthNumber))) / 10000 - : 0 - const usedCreationLastMonth = map.get(Number(lastMonthNumber)) - ? Number(map.get(Number(lastMonthNumber))) / 10000 - : 0 - - const usedCreationCurrentMonth = map.get(Number(currentMonthNumber)) - ? Number(map.get(Number(currentMonthNumber))) / 10000 - : 0 - - return [ - 1000 - usedCreationBeforeLastMonth, - 1000 - usedCreationLastMonth, - 1000 - usedCreationCurrentMonth, - ] + } + }) } function updateCreations(creations: number[], pendingCreation: AdminPendingCreation): number[] { - const dateMonth = moment().format('YYYY-MM') - const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM') - const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') - const creationDateMonth = moment(pendingCreation.date).format('YYYY-MM') + const index = getCreationIndex(pendingCreation.date.getMonth()) - switch (creationDateMonth) { - case dateMonth: - creations[2] += parseInt(pendingCreation.amount.toString()) - break - case dateLastMonth: - creations[1] += parseInt(pendingCreation.amount.toString()) - break - case dateBeforeLastMonth: - creations[0] += parseInt(pendingCreation.amount.toString()) - break - default: - throw new Error('UpdatedCreationDate is not in the last three months') + if (index < 0) { + throw new Error('You cannot create GDD for a month older than the last three months.') } + creations[index] += parseInt(pendingCreation.amount.toString()) return creations } function isCreationValid(creations: number[], amount: number, creationDate: Date) { - const dateMonth = moment().format('YYYY-MM') - const dateLastMonth = moment().subtract(1, 'month').format('YYYY-MM') - const dateBeforeLastMonth = moment().subtract(2, 'month').format('YYYY-MM') - const creationDateMonth = moment(creationDate).format('YYYY-MM') + const index = getCreationIndex(creationDate.getMonth()) - let openCreation - switch (creationDateMonth) { - case dateMonth: - openCreation = creations[2] - break - case dateLastMonth: - openCreation = creations[1] - break - case dateBeforeLastMonth: - openCreation = creations[0] - break - default: - throw new Error('CreationDate is not in last three months') + if (index < 0) { + throw new Error(`No Creation found!`) } - if (openCreation < amount) { - throw new Error(`Open creation (${openCreation}) is less than amount (${amount})`) + if (amount > creations[index]) { + throw new Error( + `The amount (${amount} GDD) to be created exceeds the available amount (${creations[index]} GDD) for this month.`, + ) } + return true } + +const getCreationMonths = (): number[] => { + const now = new Date(Date.now()) + return [ + now.getMonth() + 1, + new Date(now.getFullYear(), now.getMonth() - 1, 1).getMonth() + 1, + new Date(now.getFullYear(), now.getMonth() - 2, 1).getMonth() + 1, + ].reverse() +} + +const getCreationIndex = (month: number): number => { + return getCreationMonths().findIndex((el) => el === month + 1) +} diff --git a/backend/yarn.lock b/backend/yarn.lock index 6ef0d5701..c76b5f00f 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -4092,11 +4092,6 @@ module-alias@^2.2.2: resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0" integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q== -moment@^2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"