Merge remote-tracking branch 'origin/master' into 2632-feature-dockerfile-for-federation

This commit is contained in:
Claus-Peter Hübner 2023-02-10 21:05:42 +01:00
commit e14d42b4ea
27 changed files with 415 additions and 211 deletions

View File

@ -4,8 +4,57 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [1.18.2](https://github.com/gradido/gradido/compare/1.18.1...1.18.2)
- fix(admin): deny contribution button to left [`#2699`](https://github.com/gradido/gradido/pull/2699)
#### [1.18.1](https://github.com/gradido/gradido/compare/1.18.0...1.18.1)
> 10 February 2023
- chore(release): version 1.18.1 [`#2698`](https://github.com/gradido/gradido/pull/2698)
- fix(frontend): fix is last month for empty form date [`#2697`](https://github.com/gradido/gradido/pull/2697)
- fix(frontend): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
#### [1.18.0](https://github.com/gradido/gradido/compare/1.17.1...1.18.0)
> 9 February 2023
- feat(release): version 1.18.0 [`#2690`](https://github.com/gradido/gradido/pull/2690)
- refactor(frontend): toast by automatically logged out [`#2681`](https://github.com/gradido/gradido/pull/2681)
- refactor(frontend): change text for gdd_per_link.choose-amount [`#2638`](https://github.com/gradido/gradido/pull/2638)
- fix(backend): emails for deny and delete contribution [`#2688`](https://github.com/gradido/gradido/pull/2688)
- refactor(other): remove config version from `.env.dist` [`#2686`](https://github.com/gradido/gradido/pull/2686)
- refactor(backend): use LogError on contributionMessageResolver [`#2663`](https://github.com/gradido/gradido/pull/2663)
- refactor(backend): get last transaction by only one function [`#2668`](https://github.com/gradido/gradido/pull/2668)
- refactor(other): don't rebuild modul if unit test file has been changed [`#2667`](https://github.com/gradido/gradido/pull/2667)
- refactor(backend): use LogError on contributionLinkResolver [`#2662`](https://github.com/gradido/gradido/pull/2662)
- refactor(backend): remove event protocol config switch [`#2670`](https://github.com/gradido/gradido/pull/2670)
- refactor(backend): event protocol [`#2652`](https://github.com/gradido/gradido/pull/2652)
- refactor(frontend): sidebar becomes smaller when critical phase [`#2649`](https://github.com/gradido/gradido/pull/2649)
- refactor(backend): use LogError on sendEMailTranslated [`#2656`](https://github.com/gradido/gradido/pull/2656)
- refactor(backend): log error class [`#2640`](https://github.com/gradido/gradido/pull/2640)
- feat(backend): add filterState parameter to listAllContributions query [`#2619`](https://github.com/gradido/gradido/pull/2619)
- refactor(frontend): there is no message when a month is fully created [`#2626`](https://github.com/gradido/gradido/pull/2626)
- refactor(frontend): better text alignment on send via link [`#2637`](https://github.com/gradido/gradido/pull/2637)
- refactor(frontend): text changed as indicated in the issues [`#2642`](https://github.com/gradido/gradido/pull/2642)
- refactor(frontend): when you click on create, you will be directed to the form [`#2645`](https://github.com/gradido/gradido/pull/2645)
- feat(backend): federation: separated dht-hub features in new dht-node modul [`#2510`](https://github.com/gradido/gradido/pull/2510)
- refactor(backend): refine assembly of error message in user resolver [`#2636`](https://github.com/gradido/gradido/pull/2636)
- fix(backend): unit tests creations for 31st day [`#2641`](https://github.com/gradido/gradido/pull/2641)
- fix(workflow): properly lint pr - prevent requirement to restart linting [`#2635`](https://github.com/gradido/gradido/pull/2635)
- feat(frontend): 'yes'-button shows which dialog is currently open with a different color [`#2629`](https://github.com/gradido/gradido/pull/2629)
- feat(backend): federation implement multiple apollo graphql endpoints [`#2459`](https://github.com/gradido/gradido/pull/2459)
- refactor(frontend): add legend to all contribution tab, and add tests. [`#2625`](https://github.com/gradido/gradido/pull/2625)
- feat(frontend): unit tests community page [`#2587`](https://github.com/gradido/gradido/pull/2587)
- feat(backend): deny contributions [`#2461`](https://github.com/gradido/gradido/pull/2461)
- refactor(admin): update yarn.lock file of admin. [`#2579`](https://github.com/gradido/gradido/pull/2579)
#### [1.17.1](https://github.com/gradido/gradido/compare/1.17.0...1.17.1) #### [1.17.1](https://github.com/gradido/gradido/compare/1.17.0...1.17.1)
> 20 January 2023
- chore(release): v1.17.1 [`#2588`](https://github.com/gradido/gradido/pull/2588)
- refactor(frontend): change contribution memo add word-break [`#2583`](https://github.com/gradido/gradido/pull/2583) - refactor(frontend): change contribution memo add word-break [`#2583`](https://github.com/gradido/gradido/pull/2583)
- refactor(admin): add text-break on all table memo fields [`#2584`](https://github.com/gradido/gradido/pull/2584) - refactor(admin): add text-break on all table memo fields [`#2584`](https://github.com/gradido/gradido/pull/2584)
- fix(frontend): throw proper frontend warning errors [`#2586`](https://github.com/gradido/gradido/pull/2586) - fix(frontend): throw proper frontend warning errors [`#2586`](https://github.com/gradido/gradido/pull/2586)

View File

@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido", "description": "Administraion Interface for Gradido",
"main": "index.js", "main": "index.js",
"author": "Moriz Wahl", "author": "Moriz Wahl",
"version": "1.17.1", "version": "1.18.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": false, "private": false,
"scripts": { "scripts": {

View File

@ -259,7 +259,7 @@ describe('CreationConfirm', () => {
describe('deny creation', () => { describe('deny creation', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.findAll('tr').at(1).findAll('button').at(2).trigger('click') await wrapper.findAll('tr').at(1).findAll('button').at(1).trigger('click')
}) })
it('opens the overlay', () => { it('opens the overlay', () => {

View File

@ -129,6 +129,7 @@ export default {
fields() { fields() {
return [ return [
{ key: 'bookmark', label: this.$t('delete') }, { key: 'bookmark', label: this.$t('delete') },
{ key: 'deny', label: this.$t('deny') },
{ key: 'email', label: this.$t('e_mail') }, { key: 'email', label: this.$t('e_mail') },
{ key: 'firstName', label: this.$t('firstname') }, { key: 'firstName', label: this.$t('firstname') },
{ key: 'lastName', label: this.$t('lastname') }, { key: 'lastName', label: this.$t('lastname') },
@ -149,7 +150,6 @@ export default {
}, },
{ key: 'moderator', label: this.$t('moderator') }, { key: 'moderator', label: this.$t('moderator') },
{ key: 'editCreation', label: this.$t('edit') }, { key: 'editCreation', label: this.$t('edit') },
{ key: 'deny', label: this.$t('deny') },
{ key: 'confirm', label: this.$t('save') }, { key: 'confirm', label: this.$t('save') },
] ]
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-backend", "name": "gradido-backend",
"version": "1.17.1", "version": "1.18.2",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions", "description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend", "repository": "https://github.com/gradido/gradido/backend",
@ -74,6 +74,8 @@
"typescript": "^4.3.4" "typescript": "^4.3.4"
}, },
"nodemonConfig": { "nodemonConfig": {
"ignore": ["**/*.test.ts"] "ignore": [
"**/*.test.ts"
]
} }
} }

View File

@ -10,6 +10,7 @@ import {
sendAccountMultiRegistrationEmail, sendAccountMultiRegistrationEmail,
sendContributionConfirmedEmail, sendContributionConfirmedEmail,
sendContributionDeniedEmail, sendContributionDeniedEmail,
sendContributionDeletedEmail,
sendResetPasswordEmail, sendResetPasswordEmail,
sendTransactionLinkRedeemedEmail, sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail, sendTransactionReceivedEmail,
@ -438,6 +439,84 @@ describe('sendEmailVariants', () => {
}) })
}) })
describe('sendContributionDeletedEmail', () => {
beforeAll(async () => {
result = await sendContributionDeletedEmail({
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
language: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
})
})
describe('calls "sendEmailTranslated"', () => {
it('with expected parameters', () => {
expect(sendEmailTranslated).toBeCalledWith({
receiver: {
to: 'Peter Lustig <peter@lustig.de>',
},
template: 'contributionDeleted',
locals: {
firstName: 'Peter',
lastName: 'Lustig',
locale: 'en',
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
})
it('has expected result', () => {
expect(result).toMatchObject({
envelope: {
from: 'info@gradido.net',
to: ['peter@lustig.de'],
},
message: expect.any(String),
originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (do not answer) <info@gradido.net>',
attachments: [],
subject: 'Gradido: Your common good contribution was deleted',
html: expect.any(String),
text: expect.stringContaining('GRADIDO: YOUR COMMON GOOD CONTRIBUTION WAS DELETED'),
}),
})
expect(result.originalMessage.html).toContain('<!DOCTYPE html>')
expect(result.originalMessage.html).toContain('<html lang="en">')
expect(result.originalMessage.html).toContain(
'<title>Gradido: Your common good contribution was deleted</title>',
)
expect(result.originalMessage.html).toContain(
'>Gradido: Your common good contribution was deleted</h1>',
)
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
expect(result.originalMessage.html).toContain(
'Your public good contribution “My contribution.” was deleted by Bibi Bloxberg.',
)
expect(result.originalMessage.html).toContain(
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
)
expect(result.originalMessage.html).toContain(
`Link to your account: <a href="${CONFIG.EMAIL_LINK_OVERVIEW}">${CONFIG.EMAIL_LINK_OVERVIEW}</a>`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
expect(result.originalMessage.html).toContain('Kind regards,<br>your Gradido team')
expect(result.originalMessage.html).toContain('—————')
expect(result.originalMessage.html).toContain(
'<div style="position: relative; left: -22px;"><img src="https://gdd.gradido.net/img/brand/green.png" width="200" alt="Gradido-Akademie Logo"></div><br>Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><a href="mailto:support@supportmail.com">support@supportmail.com</a><br><a href="http://localhost/">http://localhost/</a>',
)
})
})
})
describe('sendResetPasswordEmail', () => { describe('sendResetPasswordEmail', () => {
beforeAll(async () => { beforeAll(async () => {
result = await sendResetPasswordEmail({ result = await sendResetPasswordEmail({

View File

@ -103,6 +103,32 @@ export const sendContributionConfirmedEmail = (data: {
}) })
} }
export const sendContributionDeletedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendContributionDeniedEmail = (data: { export const sendContributionDeniedEmail = (data: {
firstName: string firstName: string
lastName: string lastName: string

View File

@ -0,0 +1,16 @@
doctype html
html(lang=locale)
head
title= t('emails.contributionDeleted.subject')
body
h1(style='margin-bottom: 24px;')= t('emails.contributionDeleted.subject')
#container.col
include ../hello.pug
p= t('emails.contributionDeleted.commonGoodContributionDeleted', { senderFirstName, senderLastName, contributionMemo })
p= t('emails.contributionDeleted.toSeeContributionsAndMessages')
p
= t('emails.general.linkToYourAccount')
= " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
include ../greatingFormularImprint.pug

View File

@ -0,0 +1 @@
= t('emails.contributionDeleted.subject')

View File

@ -67,6 +67,7 @@ export class EventTransactionReceiveRedeem extends EventBasicTxX {}
export class EventContributionCreate extends EventBasicCt {} export class EventContributionCreate extends EventBasicCt {}
export class EventAdminContributionCreate extends EventBasicCt {} export class EventAdminContributionCreate extends EventBasicCt {}
export class EventAdminContributionDelete extends EventBasicCt {} export class EventAdminContributionDelete extends EventBasicCt {}
export class EventAdminContributionDeny extends EventBasicCt {}
export class EventAdminContributionUpdate extends EventBasicCt {} export class EventAdminContributionUpdate extends EventBasicCt {}
export class EventUserCreateContributionMessage extends EventBasicCtMsg {} export class EventUserCreateContributionMessage extends EventBasicCtMsg {}
export class EventAdminCreateContributionMessage extends EventBasicCtMsg {} export class EventAdminCreateContributionMessage extends EventBasicCtMsg {}
@ -298,6 +299,13 @@ export class Event {
return this return this
} }
public setEventAdminContributionDeny(ev: EventAdminContributionDeny): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.ADMIN_CONTRIBUTION_DENY
return this
}
public setEventAdminContributionUpdate(ev: EventAdminContributionUpdate): Event { public setEventAdminContributionUpdate(ev: EventAdminContributionUpdate): Event {
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount) this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
this.type = EventProtocolType.ADMIN_CONTRIBUTION_UPDATE this.type = EventProtocolType.ADMIN_CONTRIBUTION_UPDATE

View File

@ -35,6 +35,7 @@ export enum EventProtocolType {
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE', CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE', ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE', ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
ADMIN_CONTRIBUTION_DENY = 'ADMIN_CONTRIBUTION_DENY',
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE', ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE', USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE', ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',

View File

@ -22,11 +22,7 @@ import {
listContributions, listContributions,
listUnconfirmedContributions, listUnconfirmedContributions,
} from '@/seeds/graphql/queries' } from '@/seeds/graphql/queries'
import { import { sendContributionConfirmedEmail } from '@/emails/sendEmailVariants'
// sendAccountActivationEmail,
sendContributionConfirmedEmail,
// sendContributionRejectedEmail,
} from '@/emails/sendEmailVariants'
import { import {
cleanDB, cleanDB,
resetToken, resetToken,
@ -47,7 +43,6 @@ import { EventProtocolType } from '@/event/EventProtocolType'
import { logger, i18n as localization } from '@test/testSetup' import { logger, i18n as localization } from '@test/testSetup'
import { UserInputError } from 'apollo-server-express' import { UserInputError } from 'apollo-server-express'
// mock account activation email to avoid console spam
// mock account activation email to avoid console spam // mock account activation email to avoid console spam
jest.mock('@/emails/sendEmailVariants', () => { jest.mock('@/emails/sendEmailVariants', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants') const originalModule = jest.requireActual('@/emails/sendEmailVariants')

View File

@ -44,12 +44,14 @@ import {
EventContributionConfirm, EventContributionConfirm,
EventAdminContributionCreate, EventAdminContributionCreate,
EventAdminContributionDelete, EventAdminContributionDelete,
EventAdminContributionDeny,
EventAdminContributionUpdate, EventAdminContributionUpdate,
} from '@/event/Event' } from '@/event/Event'
import { writeEvent } from '@/event/EventProtocolEmitter' import { writeEvent } from '@/event/EventProtocolEmitter'
import { calculateDecay } from '@/util/decay' import { calculateDecay } from '@/util/decay'
import { import {
sendContributionConfirmedEmail, sendContributionConfirmedEmail,
sendContributionDeletedEmail,
sendContributionDeniedEmail, sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants' } from '@/emails/sendEmailVariants'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
@ -548,7 +550,7 @@ export class ContributionResolver {
eventAdminContributionDelete.amount = contribution.amount eventAdminContributionDelete.amount = contribution.amount
eventAdminContributionDelete.contributionId = contribution.id eventAdminContributionDelete.contributionId = contribution.id
await writeEvent(event.setEventAdminContributionDelete(eventAdminContributionDelete)) await writeEvent(event.setEventAdminContributionDelete(eventAdminContributionDelete))
sendContributionDeniedEmail({ sendContributionDeletedEmail({
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
email: user.emailContact.email, email: user.emailContact.email,
@ -764,6 +766,13 @@ export class ContributionResolver {
contributionToUpdate.deniedAt = new Date() contributionToUpdate.deniedAt = new Date()
const res = await contributionToUpdate.save() const res = await contributionToUpdate.save()
const event = new Event()
const eventAdminContributionDeny = new EventAdminContributionDeny()
eventAdminContributionDeny.userId = contributionToUpdate.userId
eventAdminContributionDeny.amount = contributionToUpdate.amount
eventAdminContributionDeny.contributionId = contributionToUpdate.id
await writeEvent(event.setEventAdminContributionDeny(eventAdminContributionDeny))
sendContributionDeniedEmail({ sendContributionDeniedEmail({
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,

View File

@ -23,8 +23,13 @@
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.", "commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt" "subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt"
}, },
"contributionRejected": { "contributionDeleted": {
"commonGoodContributionRejected": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.", "commonGoodContributionDeleted": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} gelöscht.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde gelöscht",
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
},
"contributionDenied": {
"commonGoodContributionDenied": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde von {senderFirstName} {senderLastName} abgelehnt.",
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde abgelehnt", "subject": "Gradido: Dein Gemeinwohl-Beitrag wurde abgelehnt",
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!" "toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
}, },

View File

@ -23,6 +23,11 @@
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.", "commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
"subject": "Gradido: Your contribution to the common good was confirmed" "subject": "Gradido: Your contribution to the common good was confirmed"
}, },
"contributionDeleted": {
"commonGoodContributionDeleted": "Your public good contribution “{contributionMemo}” was deleted by {senderFirstName} {senderLastName}.",
"subject": "Gradido: Your common good contribution was deleted",
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
},
"contributionDenied": { "contributionDenied": {
"commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.", "commonGoodContributionDenied": "Your public good contribution “{contributionMemo}” was rejected by {senderFirstName} {senderLastName}.",
"subject": "Gradido: Your common good contribution was rejected", "subject": "Gradido: Your common good contribution was rejected",

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido-database", "name": "gradido-database",
"version": "1.17.1", "version": "1.18.2",
"description": "Gradido Database Tool to execute database migrations", "description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts", "main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database", "repository": "https://github.com/gradido/gradido/database",

View File

@ -1,6 +1,6 @@
{ {
"name": "bootstrap-vue-gradido-wallet", "name": "bootstrap-vue-gradido-wallet",
"version": "1.17.1", "version": "1.18.2",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node run/server.js", "start": "node run/server.js",

View File

@ -24,11 +24,6 @@ describe('ContributionForm', () => {
$t: jest.fn((t) => t), $t: jest.fn((t) => t),
$d: jest.fn((d) => d), $d: jest.fn((d) => d),
$n: jest.fn((n) => n), $n: jest.fn((n) => n),
$store: {
state: {
creation: ['1000', '1000', '1000'],
},
},
$i18n: { $i18n: {
locale: 'en', locale: 'en',
}, },
@ -61,7 +56,7 @@ describe('ContributionForm', () => {
}) })
}) })
describe('dates', () => { describe('dates and max amounts', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.setData({ await wrapper.setData({
form: { form: {
@ -73,204 +68,176 @@ describe('ContributionForm', () => {
}) })
}) })
describe('actual date', () => { describe('max amount reached for both months', () => {
describe('same month', () => { beforeEach(() => {
wrapper.setProps({
maxGddLastMonth: 0,
maxGddThisMonth: 0,
})
wrapper.setData({
form: {
id: null,
date: 'set',
memo: '',
amount: '',
},
})
})
it('shows message that no contributions are available', () => {
expect(wrapper.find('[data-test="contribtion-message"]').text()).toBe(
'contribution.noOpenCreation.allMonth',
)
})
})
describe('max amount reached for last month, no date selected', () => {
beforeEach(() => {
wrapper.setProps({
maxGddLastMonth: 0,
})
})
it('shows no message', () => {
expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
})
})
describe('max amount reached for last month, last month selected', () => {
beforeEach(async () => { beforeEach(async () => {
const now = new Date().toISOString() wrapper.setProps({
await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now) maxGddLastMonth: 0,
isThisMonth: false,
}) })
describe('isThisMonth', () => {
it('has true', () => {
expect(wrapper.vm.isThisMonth).toBe(true)
})
})
})
describe.skip('month before', () => {
beforeEach(async () => {
await wrapper
.findComponent({ name: 'BFormDatepicker' })
.vm.$emit('input', wrapper.vm.minimalDate)
})
describe('isThisMonth', () => {
it('has false', () => {
expect(wrapper.vm.isThisMonth).toBe(false)
})
})
})
})
describe.skip('date in middle of year', () => {
describe('same month', () => {
beforeEach(async () => {
// jest.useFakeTimers('modern')
// jest.setSystemTime(new Date('2020-07-06'))
// await wrapper.findComponent({ name: 'BFormDatepicker' }).vm.$emit('input', now)
await wrapper.setData({ await wrapper.setData({
maximalDate: new Date(2020, 6, 6), form: {
form: { date: new Date(2020, 6, 6) }, id: null,
date: 'set',
memo: '',
amount: '',
},
}) })
}) })
describe('minimalDate', () => { it('shows message that no contributions are available for last month', () => {
it('has "2020-06-01T00:00:00.000Z"', () => { expect(wrapper.find('[data-test="contribtion-message"]').text()).toBe(
expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z') 'contribution.noOpenCreation.lastMonth',
)
}) })
}) })
describe('isThisMonth', () => { describe('max amount reached for last month, this month selected', () => {
it('has true', () => {
expect(wrapper.vm.isThisMonth).toBe(true)
})
})
})
describe('month before', () => {
beforeEach(async () => { beforeEach(async () => {
// jest.useFakeTimers('modern') wrapper.setProps({
// jest.setSystemTime(new Date('2020-07-06')) maxGddLastMonth: 0,
// console.log('middle of year date now:', wrapper.vm.minimalDate) isThisMonth: true,
// await wrapper })
// .findComponent({ name: 'BFormDatepicker' })
// .vm.$emit('input', wrapper.vm.minimalDate)
await wrapper.setData({ await wrapper.setData({
maximalDate: new Date(2020, 6, 6), form: {
form: { date: new Date(2020, 5, 6) }, id: null,
date: 'set',
memo: '',
amount: '',
},
}) })
}) })
describe('minimalDate', () => { it('shows no message', () => {
it('has "2020-06-01T00:00:00.000Z"', () => { expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
expect(wrapper.vm.minimalDate.toISOString()).toBe('2020-06-01T00:00:00.000Z')
}) })
}) })
describe('isThisMonth', () => { describe('max amount reached for this month, no date selected', () => {
it('has false', () => { beforeEach(() => {
expect(wrapper.vm.isThisMonth).toBe(false) wrapper.setProps({
}) maxGddThisMonth: 0,
})
}) })
}) })
describe.skip('date in january', () => { it('shows no message', () => {
describe('same month', () => { expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
})
})
describe('max amount reached for this month, this month selected', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper.setProps({
maxGddThisMonth: 0,
isThisMonth: true,
})
await wrapper.setData({ await wrapper.setData({
maximalDate: new Date(2020, 0, 6), form: {
form: { date: new Date(2020, 0, 6) }, id: null,
date: 'set',
memo: '',
amount: '',
},
}) })
}) })
describe('minimalDate', () => { it('shows message that no contributions are available for last month', () => {
it('has "2019-12-01T00:00:00.000Z"', () => { expect(wrapper.find('[data-test="contribtion-message"]').text()).toBe(
expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z') 'contribution.noOpenCreation.thisMonth',
)
}) })
}) })
describe('isThisMonth', () => { describe('max amount reached for this month, last month selected', () => {
it('has true', () => {
expect(wrapper.vm.isThisMonth).toBe(true)
})
})
})
describe('month before', () => {
beforeEach(async () => { beforeEach(async () => {
// jest.useFakeTimers('modern') wrapper.setProps({
// jest.setSystemTime(new Date('2020-07-06')) maxGddThisMonth: 0,
// console.log('middle of year date now:', wrapper.vm.minimalDate) isThisMonth: false,
// await wrapper })
// .findComponent({ name: 'BFormDatepicker' })
// .vm.$emit('input', wrapper.vm.minimalDate)
await wrapper.setData({ await wrapper.setData({
maximalDate: new Date(2020, 0, 6), form: {
form: { date: new Date(2019, 11, 6) }, id: null,
date: 'set',
memo: '',
amount: '',
},
}) })
}) })
describe('minimalDate', () => { it('shows no message', () => {
it('has "2019-12-01T00:00:00.000Z"', () => { expect(wrapper.find('[data-test="contribtion-message"]').exists()).toBe(false)
expect(wrapper.vm.minimalDate.toISOString()).toBe('2019-12-01T00:00:00.000Z')
})
})
describe('isThisMonth', () => {
it('has false', () => {
expect(wrapper.vm.isThisMonth).toBe(false)
})
}) })
}) })
}) })
describe.skip('date with the 31st day of the month', () => { describe('default return message', () => {
describe('same month', () => { it('returns an empty string', () => {
beforeEach(async () => { expect(wrapper.vm.noOpenCreation).toBe('')
await wrapper.setData({
maximalDate: new Date('2022-10-31T00:00:00.000Z'),
form: { date: new Date('2022-10-31T00:00:00.000Z') },
}) })
}) })
describe('minimalDate', () => { describe('update amount', () => {
it('has "2022-09-01T00:00:00.000Z"', () => { beforeEach(() => {
expect(wrapper.vm.minimalDate.toISOString()).toBe('2022-09-01T00:00:00.000Z') wrapper.findComponent({ name: 'InputHour' }).vm.$emit('updateAmount', 20)
})
it('updates form amount', () => {
expect(wrapper.vm.form.amount).toBe('400.00')
}) })
}) })
describe('isThisMonth', () => { describe('watch value', () => {
it('has true', () => { beforeEach(() => {
expect(wrapper.vm.isThisMonth).toBe(true) wrapper.setProps({
}) value: {
}) id: 42,
date: 'set',
memo: 'Some Memo',
amount: '400.00',
},
}) })
}) })
describe.skip('date with the 28th day of the month', () => { it('updates form', () => {
describe('same month', () => { expect(wrapper.vm.form).toEqual({
beforeEach(async () => { id: 42,
await wrapper.setData({ date: 'set',
maximalDate: new Date('2023-02-28T00:00:00.000Z'), memo: 'Some Memo',
form: { date: new Date('2023-02-28T00:00:00.000Z') }, amount: '400.00',
})
})
describe('minimalDate', () => {
it('has "2023-01-01T00:00:00.000Z"', () => {
expect(wrapper.vm.minimalDate.toISOString()).toBe('2023-01-01T00:00:00.000Z')
})
})
describe('isThisMonth', () => {
it('has true', () => {
expect(wrapper.vm.isThisMonth).toBe(true)
})
})
})
})
describe.skip('date with 29.02.2024 leap year', () => {
describe('same month', () => {
beforeEach(async () => {
await wrapper.setData({
maximalDate: new Date('2024-02-29T00:00:00.000Z'),
form: { date: new Date('2024-02-29T00:00:00.000Z') },
})
})
describe('minimalDate', () => {
it('has "2024-01-01T00:00:00.000Z"', () => {
expect(wrapper.vm.minimalDate.toISOString()).toBe('2024-01-01T00:00:00.000Z')
})
})
describe('isThisMonth', () => {
it('has true', () => {
expect(wrapper.vm.isThisMonth).toBe(true)
})
})
}) })
}) })
}) })
@ -477,24 +444,23 @@ describe('ContributionForm', () => {
}) })
}) })
describe.skip('on trigger submit', () => { describe('on trigger submit', () => {
beforeEach(async () => { beforeEach(async () => {
await wrapper.find('form').trigger('submit') await wrapper.find('form').trigger('submit')
}) })
it('emits "update-contribution"', () => { it('emits "update-contribution"', () => {
expect(wrapper.emitted('update-contribution')).toEqual( expect(wrapper.emitted('update-contribution')).toEqual([
expect.arrayContaining([ [
expect.arrayContaining([
{ {
id: 2, id: 2,
date: now, date: now,
hours: 0,
memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...', memo: 'Mein Beitrag zur Gemeinschaft für diesen Monat ...',
amount: '200', amount: '200',
}, },
]), ],
]), ])
)
}) })
}) })
}) })

View File

@ -23,10 +23,7 @@
<template #nav-next-year><span></span></template> <template #nav-next-year><span></span></template>
</b-form-datepicker> </b-form-datepicker>
<div <div v-if="showMessage" class="p-3" data-test="contribtion-message">
v-if="(isThisMonth && maxGddThisMonth <= 0) || (!isThisMonth && maxGddLastMonth <= 0)"
class="p-3"
>
{{ noOpenCreation }} {{ noOpenCreation }}
</div> </div>
<div v-else> <div v-else>
@ -118,8 +115,8 @@ export default {
} }
}, },
methods: { methods: {
updateAmount(amount) { updateAmount(hours) {
this.form.amount = (amount * 20).toFixed(2).toString() this.form.amount = (hours * 20).toFixed(2).toString()
}, },
submit() { submit() {
this.$emit(this.form.id ? 'update-contribution' : 'set-contribution', { ...this.form }) this.$emit(this.form.id ? 'update-contribution' : 'set-contribution', { ...this.form })
@ -135,6 +132,15 @@ export default {
}, },
}, },
computed: { computed: {
showMessage() {
if (this.maxGddThisMonth <= 0 && this.maxGddLastMonth <= 0) return true
if (this.form.date)
return (
(this.isThisMonth && this.maxGddThisMonth <= 0) ||
(!this.isThisMonth && this.maxGddLastMonth <= 0)
)
return false
},
disabled() { disabled() {
return ( return (
this.form.date === '' || this.form.date === '' ||

View File

@ -116,5 +116,15 @@ describe('ContributionList', () => {
expect(wrapper.emitted('delete-contribution')).toEqual([[{ id: 2 }]]) expect(wrapper.emitted('delete-contribution')).toEqual([[{ id: 2 }]])
}) })
}) })
describe('update status', () => {
beforeEach(() => {
wrapper.findComponent({ name: 'ContributionListItem' }).vm.$emit('update-state', { id: 2 })
})
it('emits update status', () => {
expect(wrapper.emitted('update-state')).toEqual([[{ id: 2 }]])
})
})
}) })
}) })

View File

@ -67,6 +67,7 @@ export default {
} }
if (this.tokenExpiresInSeconds === 0) { if (this.tokenExpiresInSeconds === 0) {
this.$timer.stop('tokenExpires') this.$timer.stop('tokenExpires')
this.toastInfoNoHide(this.$t('session.automaticallyLoggedOut'))
this.$emit('logout') this.$emit('logout')
} }
}, },
@ -84,6 +85,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.$timer.stop('tokenExpires') this.$timer.stop('tokenExpires')
this.toastInfoNoHide(this.$t('session.automaticallyLoggedOut'))
this.$emit('logout') this.$emit('logout')
}) })
}, },

View File

@ -173,7 +173,7 @@
"GDD": "GDD", "GDD": "GDD",
"gddKonto": "GDD Konto", "gddKonto": "GDD Konto",
"gdd_per_link": { "gdd_per_link": {
"choose-amount": "Wähle einen Betrag aus, welchen du per Link versenden möchtest. Du kannst auch noch eine Nachricht eintragen. Beim Klick „Jetzt generieren“ wird ein Link erstellt, den du versenden kannst.", "choose-amount": "Wähle einen Betrag aus, welchen du per Link versenden möchtest, und trage eine Nachricht ein. Die Nachricht ist ein Pflichtfeld.",
"copy-link": "Link kopieren", "copy-link": "Link kopieren",
"copy-link-with-text": "Link und Text kopieren", "copy-link-with-text": "Link und Text kopieren",
"created": "Der Link wurde erstellt!", "created": "Der Link wurde erstellt!",
@ -272,6 +272,7 @@
"send_gdd": "GDD versenden", "send_gdd": "GDD versenden",
"send_per_link": "GDD versenden per Link", "send_per_link": "GDD versenden per Link",
"session": { "session": {
"automaticallyLoggedOut": "Du wurdest automatisch abgemeldet",
"extend": "Angemeldet bleiben", "extend": "Angemeldet bleiben",
"lightText": "Wenn du länger als 10 Minuten keine Aktion getätigt hast, wirst du aus Sicherheitsgründen abgemeldet.", "lightText": "Wenn du länger als 10 Minuten keine Aktion getätigt hast, wirst du aus Sicherheitsgründen abgemeldet.",
"logoutIn": "Abmelden in ", "logoutIn": "Abmelden in ",

View File

@ -173,7 +173,7 @@
"GDD": "GDD", "GDD": "GDD",
"gddKonto": "GDD Konto", "gddKonto": "GDD Konto",
"gdd_per_link": { "gdd_per_link": {
"choose-amount": "Select an amount that you would like to send via link. You can also enter a message. Click 'Generate now' to create a link that you can share.", "choose-amount": "Select an amount you want to send via link and enter a message. The message is mandatory.",
"copy-link": "Copy link", "copy-link": "Copy link",
"copy-link-with-text": "Copy link and text", "copy-link-with-text": "Copy link and text",
"created": "Link was created!", "created": "Link was created!",
@ -272,6 +272,7 @@
"send_gdd": "Send GDD", "send_gdd": "Send GDD",
"send_per_link": "Send GDD via Link", "send_per_link": "Send GDD via Link",
"session": { "session": {
"automaticallyLoggedOut": "You have been automatically logged out.",
"extend": "Stay logged in", "extend": "Stay logged in",
"lightText": "If you have not performed any action for more than 10 minutes, you will be logged out for security reasons.", "lightText": "If you have not performed any action for more than 10 minutes, you will be logged out for security reasons.",
"logoutIn": "Log out in ", "logoutIn": "Log out in ",

View File

@ -18,11 +18,18 @@ export const toasters = {
variant: 'warning', variant: 'warning',
}) })
}, },
toastInfoNoHide(message) {
this.toast(message, {
title: this.$t('navigation.info'),
variant: 'warning',
noAutoHide: true,
})
},
toast(message, options) { toast(message, options) {
if (message.replace) message = message.replace(/^GraphQL error: /, '') if (message.replace) message = message.replace(/^GraphQL error: /, '')
this.$root.$bvToast.toast(message, { this.$root.$bvToast.toast(message, {
autoHideDelay: 5000,
appendToast: true, appendToast: true,
autoHideDelay: 5000,
solid: true, solid: true,
toaster: 'b-toaster-top-right', toaster: 'b-toaster-top-right',
headerClass: 'gdd-toaster-title', headerClass: 'gdd-toaster-title',

View File

@ -70,6 +70,8 @@ describe('Community', () => {
lastName: 'Bloxberg', lastName: 'Bloxberg',
state: 'IN_PROGRESS', state: 'IN_PROGRESS',
messagesCount: 0, messagesCount: 0,
deniedAt: null,
deniedBy: null,
}, },
{ {
id: 1550, id: 1550,
@ -84,6 +86,8 @@ describe('Community', () => {
lastName: 'Bloxberg', lastName: 'Bloxberg',
state: 'CONFIRMED', state: 'CONFIRMED',
messagesCount: 0, messagesCount: 0,
deniedAt: null,
deniedBy: null,
}, },
], ],
contributionCount: 1, contributionCount: 1,
@ -112,6 +116,10 @@ describe('Community', () => {
confirmedAt: null, confirmedAt: null,
firstName: 'Bibi', firstName: 'Bibi',
lastName: 'Bloxberg', lastName: 'Bloxberg',
deniedAt: null,
deniedBy: null,
messagesCount: 0,
state: 'IN_PROGRESS',
}, },
{ {
id: 1550, id: 1550,
@ -124,7 +132,10 @@ describe('Community', () => {
firstName: 'Bibi', firstName: 'Bibi',
contributionDate: '2022-06-15T08:47:06.000Z', contributionDate: '2022-06-15T08:47:06.000Z',
lastName: 'Bloxberg', lastName: 'Bloxberg',
deniedAt: null,
deniedBy: null,
messagesCount: 0, messagesCount: 0,
state: 'IN_PROGRESS',
}, },
{ {
id: 1556, id: 1556,
@ -137,6 +148,10 @@ describe('Community', () => {
confirmedAt: null, confirmedAt: null,
firstName: 'Bob', firstName: 'Bob',
lastName: 'der Baumeister', lastName: 'der Baumeister',
deniedAt: null,
deniedBy: null,
messagesCount: 0,
state: 'IN_PROGRESS',
}, },
], ],
contributionCount: 3, contributionCount: 3,

View File

@ -6,9 +6,9 @@
{{ CONFIG.COMMUNITY_DESCRIPTION }} {{ CONFIG.COMMUNITY_DESCRIPTION }}
</div> </div>
<div> <div>
<router-link :to="CONFIG.COMMUNITY_URL"> <b-link :href="CONFIG.COMMUNITY_URL">
{{ CONFIG.COMMUNITY_URL }} {{ CONFIG.COMMUNITY_URL }}
</router-link> </b-link>
</div> </div>
<hr /> <hr />
<div class="h3">{{ $t('community.openContributionLinks') }}</div> <div class="h3">{{ $t('community.openContributionLinks') }}</div>

View File

@ -1,6 +1,6 @@
{ {
"name": "gradido", "name": "gradido",
"version": "1.17.1", "version": "1.18.2",
"description": "Gradido", "description": "Gradido",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:gradido/gradido.git", "repository": "git@github.com:gradido/gradido.git",