diff --git a/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.spec.js b/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.spec.js index 7cca315d7..f8575492a 100644 --- a/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.spec.js +++ b/admin/src/components/ContributionMessages/slots/ContributionMessagesListItem.spec.js @@ -20,7 +20,7 @@ describe('ContributionMessagesListItem', () => { const propsData = { contributionId: 42, - state: 'PENDING0', + state: 'PENDING', message: { id: 111, message: 'asd asda sda sda', diff --git a/admin/src/components/CreationTransactionList.spec.js b/admin/src/components/CreationTransactionList.spec.js index 3e2d5893e..ff9607424 100644 --- a/admin/src/components/CreationTransactionList.spec.js +++ b/admin/src/components/CreationTransactionList.spec.js @@ -6,30 +6,29 @@ const localVue = global.localVue const apolloQueryMock = jest.fn().mockResolvedValue({ data: { - creationTransactionList: [ - { - id: 1, - amount: 100, - balanceDate: 0, - creationDate: new Date(), - memo: 'Testing', - linkedUser: { - firstName: 'Gradido', - lastName: 'Akademie', + creationTransactionList: { + contributionCount: 2, + contributionList: [ + { + id: 1, + amount: 5.8, + createdAt: '2022-09-21T11:09:51.000Z', + confirmedAt: null, + contributionDate: '2022-08-01T00:00:00.000Z', + memo: 'für deine Hilfe, Fräulein Rottenmeier', + state: 'PENDING', }, - }, - { - id: 2, - amount: 200, - balanceDate: 0, - creationDate: new Date(), - memo: 'Testing 2', - linkedUser: { - firstName: 'Gradido', - lastName: 'Akademie', + { + id: 2, + amount: '47', + createdAt: '2022-09-21T11:09:28.000Z', + confirmedAt: '2022-09-21T11:09:28.000Z', + contributionDate: '2022-08-01T00:00:00.000Z', + memo: 'für deine Hilfe, Frau Holle', + state: 'CONFIRMED', }, - }, - ], + ], + }, }, }) @@ -43,7 +42,7 @@ const mocks = { const propsData = { userId: 1, - fields: ['date', 'balance', 'name', 'memo', 'decay'], + fields: ['createdAt', 'contributionDate', 'confirmedAt', 'amount', 'memo'], } describe('CreationTransactionList', () => { @@ -63,7 +62,7 @@ describe('CreationTransactionList', () => { expect.objectContaining({ variables: { currentPage: 1, - pageSize: 25, + pageSize: 10, order: 'DESC', userId: 1, }, diff --git a/admin/src/components/CreationTransactionList.vue b/admin/src/components/CreationTransactionList.vue index ec5c12aa4..2ce143c7f 100644 --- a/admin/src/components/CreationTransactionList.vue +++ b/admin/src/components/CreationTransactionList.vue @@ -1,7 +1,44 @@ diff --git a/admin/src/graphql/creationTransactionList.js b/admin/src/graphql/creationTransactionList.js index 327221814..fc3a80b18 100644 --- a/admin/src/graphql/creationTransactionList.js +++ b/admin/src/graphql/creationTransactionList.js @@ -8,14 +8,15 @@ export const creationTransactionList = gql` order: $order userId: $userId ) { - id - amount - balanceDate - creationDate - memo - linkedUser { - firstName - lastName + contributionCount + contributionList { + id + amount + createdAt + confirmedAt + contributionDate + memo + state } } } diff --git a/admin/src/locales/de.json b/admin/src/locales/de.json index 55b73bbe3..65611b1ab 100644 --- a/admin/src/locales/de.json +++ b/admin/src/locales/de.json @@ -75,10 +75,20 @@ "submit": "Senden" }, "GDD": "GDD", + "help": { + "help": "Hilfe", + "transactionlist": { + "confirmed": "Wann wurde es von einem Moderator / Admin bestätigt.", + "periods": "Für welchen Zeitraum wurde vom Mitglied eingereicht.", + "state": "[PENDING = eingereicht, DELETED = gelöscht, IN_PROGRESS = im Dialog mit Moderator, DENIED = abgelehnt, CONFIRMED = bestätigt]", + "submitted": "Wann wurde es vom Mitglied eingereicht" + } + }, "hide_details": "Details verbergen", "lastname": "Nachname", "math": { "colon": ":", + "equals": "=", "exclaim": "!", "pipe": "|", "plus": "+" @@ -134,10 +144,11 @@ }, "transactionlist": { "amount": "Betrag", - "balanceDate": "Schöpfungsdatum", - "community": "Gemeinschaft", - "date": "Datum", + "confirmed": "Bestätigt", "memo": "Nachricht", + "period": "Zeitraum", + "state": "Status", + "submitted": "Eingereicht", "title": "Alle geschöpften Transaktionen für den Nutzer" }, "undelete_user": "Nutzer wiederherstellen", diff --git a/admin/src/locales/en.json b/admin/src/locales/en.json index a7221c4cb..57fd1c6b4 100644 --- a/admin/src/locales/en.json +++ b/admin/src/locales/en.json @@ -75,10 +75,20 @@ "submit": "Send" }, "GDD": "GDD", + "help": { + "help": "Help", + "transactionlist": { + "confirmed": "When was it confirmed by a moderator / admin.", + "periods": "For what period was it submitted by the member.", + "state": "[PENDING = submitted, DELETED = deleted, IN_PROGRESS = in dialogue with moderator, DENIED = denied, CONFIRMED = confirmed]", + "submitted": "When was it submitted by the member" + } + }, "hide_details": "Hide details", "lastname": "Lastname", "math": { "colon": ":", + "equals": "=", "exclaim": "!", "pipe": "|", "plus": "+" @@ -134,10 +144,11 @@ }, "transactionlist": { "amount": "Amount", - "balanceDate": "Creation date", - "community": "Community", - "date": "Date", + "confirmed": "Confirmed", "memo": "Message", + "period": "Period", + "state": "State", + "submitted": "Submitted", "title": "All creation-transactions for the user" }, "undelete_user": "Undelete User", diff --git a/backend/src/graphql/model/Contribution.ts b/backend/src/graphql/model/Contribution.ts index 1f690a3d8..cf57e632f 100644 --- a/backend/src/graphql/model/Contribution.ts +++ b/backend/src/graphql/model/Contribution.ts @@ -5,7 +5,7 @@ import { User } from '@entity/User' @ObjectType() export class Contribution { - constructor(contribution: dbContribution, user: User) { + constructor(contribution: dbContribution, user?: User | null) { this.id = contribution.id this.firstName = user ? user.firstName : null this.lastName = user ? user.lastName : null diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index e9ee0b55b..3435edb94 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -15,6 +15,7 @@ import { AdminCreateContributions } from '@model/AdminCreateContributions' import { AdminUpdateContribution } from '@model/AdminUpdateContribution' import { ContributionLink } from '@model/ContributionLink' import { ContributionLinkList } from '@model/ContributionLinkList' +import { Contribution } from '@model/Contribution' import { RIGHTS } from '@/auth/RIGHTS' import { UserRepository } from '@repository/User' import AdminCreateContributionArgs from '@arg/AdminCreateContributionArgs' @@ -23,12 +24,10 @@ import SearchUsersArgs from '@arg/SearchUsersArgs' import ContributionLinkArgs from '@arg/ContributionLinkArgs' import { Transaction as DbTransaction } from '@entity/Transaction' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' -import { Transaction } from '@model/Transaction' import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' import { TransactionLink as dbTransactionLink } from '@entity/TransactionLink' -import { TransactionRepository } from '@repository/Transaction' import { calculateDecay } from '@/util/decay' -import { Contribution } from '@entity/Contribution' +import { Contribution as DbContribution } from '@entity/Contribution' import { hasElopageBuys } from '@/util/hasElopageBuys' import { User as dbUser } from '@entity/User' import { User } from '@model/User' @@ -40,7 +39,6 @@ import { Decay } from '@model/Decay' import Paginated from '@arg/Paginated' import TransactionLinkFilters from '@arg/TransactionLinkFilters' import { Order } from '@enum/Order' -import { communityUser } from '@/util/communityUser' import { findUserByEmail, activationLink, printTimeDuration } from './UserResolver' import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver' @@ -66,6 +64,7 @@ import { ContributionMessageType } from '@enum/MessageType' import { ContributionMessage } from '@model/ContributionMessage' import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail' import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail' +import { ContributionListResult } from '../model/Contribution' // const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_UNKNOWN = 3 // elopage? @@ -248,7 +247,7 @@ export class AdminResolver { const creationDateObj = new Date(creationDate) logger.trace('creationDateObj:', creationDateObj) validateContribution(creations, amount, creationDateObj) - const contribution = Contribution.create() + const contribution = DbContribution.create() contribution.userId = emailContact.userId contribution.amount = amount contribution.createdAt = new Date() @@ -259,7 +258,7 @@ export class AdminResolver { contribution.contributionStatus = ContributionStatus.PENDING logger.trace('contribution to save', contribution) - await Contribution.save(contribution) + await DbContribution.save(contribution) return getUserCreation(emailContact.userId) } @@ -317,7 +316,7 @@ export class AdminResolver { const moderator = getUser(context) - const contributionToUpdate = await Contribution.findOne({ + const contributionToUpdate = await DbContribution.findOne({ where: { id, confirmedAt: IsNull() }, }) @@ -350,7 +349,7 @@ export class AdminResolver { contributionToUpdate.moderatorId = moderator.id contributionToUpdate.contributionStatus = ContributionStatus.PENDING - await Contribution.save(contributionToUpdate) + await DbContribution.save(contributionToUpdate) const result = new AdminUpdateContribution() result.amount = amount result.memo = contributionToUpdate.memo @@ -367,7 +366,7 @@ export class AdminResolver { const contributions = await getConnection() .createQueryBuilder() .select('c') - .from(Contribution, 'c') + .from(DbContribution, 'c') .leftJoinAndSelect('c.messages', 'm') .where({ confirmedAt: IsNull() }) .getMany() @@ -399,7 +398,7 @@ export class AdminResolver { @Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION]) @Mutation(() => Boolean) async adminDeleteContribution(@Arg('id', () => Int) id: number): Promise { - const contribution = await Contribution.findOne(id) + const contribution = await DbContribution.findOne(id) if (!contribution) { logger.error(`Contribution not found for given id: ${id}`) throw new Error('Contribution not found for given id.') @@ -416,7 +415,7 @@ export class AdminResolver { @Arg('id', () => Int) id: number, @Ctx() context: Context, ): Promise { - const contribution = await Contribution.findOne(id) + const contribution = await DbContribution.findOne(id) if (!contribution) { logger.error(`Contribution not found for given id: ${id}`) throw new Error('Contribution not found to given id.') @@ -481,7 +480,7 @@ export class AdminResolver { contribution.confirmedBy = moderatorUser.id contribution.transactionId = transaction.id contribution.contributionStatus = ContributionStatus.CONFIRMED - await queryRunner.manager.update(Contribution, { id: contribution.id }, contribution) + await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) await queryRunner.commitTransaction() logger.info('creation commited successfuly.') @@ -506,24 +505,29 @@ export class AdminResolver { } @Authorized([RIGHTS.CREATION_TRANSACTION_LIST]) - @Query(() => [Transaction]) + @Query(() => ContributionListResult) async creationTransactionList( @Args() { currentPage = 1, pageSize = 25, order = Order.DESC }: Paginated, @Arg('userId', () => Int) userId: number, - ): Promise { + ): Promise { const offset = (currentPage - 1) * pageSize - const transactionRepository = getCustomRepository(TransactionRepository) - const [userTransactions] = await transactionRepository.findByUserPaged( - userId, - pageSize, - offset, - order, - true, - ) + const [contributionResult, count] = await getConnection() + .createQueryBuilder() + .select('c') + .from(DbContribution, 'c') + .leftJoinAndSelect('c.user', 'u') + .where(`user_id = ${userId}`) + .limit(pageSize) + .offset(offset) + .orderBy('c.created_at', order) + .getManyAndCount() - const user = await dbUser.findOneOrFail({ id: userId }) - return userTransactions.map((t) => new Transaction(t, new User(user), communityUser)) + return new ContributionListResult( + count, + contributionResult.map((contribution) => new Contribution(contribution, contribution.user)), + ) + // return userTransactions.map((t) => new Transaction(t, new User(user), communityUser)) } @Authorized([RIGHTS.SEND_ACTIVATION_EMAIL]) @@ -744,7 +748,7 @@ export class AdminResolver { await queryRunner.startTransaction('REPEATABLE READ') const contributionMessage = DbContributionMessage.create() try { - const contribution = await Contribution.findOne({ + const contribution = await DbContribution.findOne({ where: { id: contributionId }, relations: ['user'], }) @@ -773,7 +777,7 @@ export class AdminResolver { contribution.contributionStatus === ContributionStatus.PENDING ) { contribution.contributionStatus = ContributionStatus.IN_PROGRESS - await queryRunner.manager.update(Contribution, { id: contributionId }, contribution) + await queryRunner.manager.update(DbContribution, { id: contributionId }, contribution) } await sendAddedContributionMessageEmail({ diff --git a/database/migrations/0049-add_user_contacts_table.ts b/database/migrations/0049-add_user_contacts_table.ts index c3b89ed88..acdd1af61 100644 --- a/database/migrations/0049-add_user_contacts_table.ts +++ b/database/migrations/0049-add_user_contacts_table.ts @@ -14,8 +14,8 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis \`type\` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, \`user_id\` int(10) unsigned NOT NULL, \`email\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE, - \`email_verification_code\` bigint(20) unsigned NOT NULL UNIQUE, - \`email_opt_in_type_id\` int NOT NULL, + \`email_verification_code\` bigint(20) unsigned DEFAULT NULL UNIQUE, + \`email_opt_in_type_id\` int DEFAULT NULL, \`email_resend_count\` int DEFAULT '0', \`email_checked\` tinyint(4) NOT NULL DEFAULT 0, \`phone\` varchar(255) COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, @@ -41,47 +41,13 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis // merge values from login_email_opt_in table with users.email in new user_contacts table await queryFn(` - INSERT INTO user_contacts - (type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at) - SELECT - 'EMAIL', - u.id as user_id, - u.email, - e.verification_code as email_verification_code, - e.email_opt_in_type_id, - e.resend_count as email_resend_count, - u.email_checked, - e.created as created_at, - e.updated as updated_at, - u.deletedAt as deleted_at\ - FROM - users as u, - login_email_opt_in as e - WHERE - u.id = e.user_id AND - e.id in ( - WITH opt_in AS ( - SELECT - le.id, le.user_id, le.created, le.updated, ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num - FROM - login_email_opt_in as le - ) - SELECT - opt_in.id - FROM - opt_in - WHERE - row_num = 1);`) - /* - // SELECT - // le.id - // FROM - // login_email_opt_in as le - // WHERE - // le.user_id = u.id - // ORDER BY - // le.updated DESC, le.created DESC LIMIT 1);`) - */ + INSERT INTO user_contacts + (type, user_id, email, email_verification_code, email_opt_in_type_id, email_resend_count, email_checked, created_at, updated_at, deleted_at) + SELECT 'EMAIL', users.id, users.email, optin.verification_code, optin.email_opt_in_type_id, optin.resend_count, users.email_checked, users.created, null, users.deletedAt + FROM users LEFT JOIN + (SELECT le.id, le.user_id, le.verification_code, le.email_opt_in_type_id, le.resend_count, le.created, le.updated, + ROW_NUMBER() OVER (PARTITION BY le.user_id ORDER BY le.created DESC) AS row_num + FROM login_email_opt_in as le) AS optin ON users.id = optin.user_id AND row_num = 1;`) // insert in users table the email_id of the new created email-contacts const contacts = await queryFn(`SELECT c.id, c.user_id FROM user_contacts as c`) @@ -113,11 +79,13 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom ) // reconstruct the previous email back from contacts to users table - const contacts = await queryFn(`SELECT c.id, c.email, c.user_id FROM user_contacts as c`) + const contacts = await queryFn( + `SELECT c.id, c.email, c.user_id, c.email_checked FROM user_contacts as c`, + ) for (const id in contacts) { const contact = contacts[id] await queryFn( - `UPDATE users SET email = "${contact.email}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`, + `UPDATE users SET email = "${contact.email}", email_checked="${contact.email_checked}" WHERE id = "${contact.user_id}" and email_id = "${contact.id}"`, ) } await queryFn('ALTER TABLE users MODIFY COLUMN email varchar(255) NOT NULL UNIQUE;')