test and fix, make code more compact

This commit is contained in:
einhorn_b 2023-11-28 21:08:27 +01:00
parent 993d364ecc
commit a36f8962e6
18 changed files with 267 additions and 142 deletions

View File

@ -1,18 +1,24 @@
<template>
<div class="contribution-messages-formular">
<div class="mt-5">
<b-form @reset.prevent="onReset" @submit="onSubmit(messageType.DIALOG)">
<b-tabs content-class="mt-3" v-model="chatOrMemo">
<b-tab :title="$t('moderator.chat')" active>
<b-form-group>
<b-form-checkbox v-model="showResubmissionDate">
{{ $t('moderator.show-submission-form') }}
</b-form-checkbox>
</b-form-group>
<b-form-group v-if="showResubmissionDate">
<b-form-datepicker v-model="resubmissionDate"></b-form-datepicker>
<time-picker v-model="resubmissionTime"></time-picker>
</b-form-group>
<b-form @reset.prevent="onReset" @submit="onSubmit()">
<b-form-group>
<b-form-checkbox v-model="showResubmissionDate">
{{ $t('moderator.show-submission-form') }}
</b-form-checkbox>
</b-form-group>
<b-form-group v-if="showResubmissionDate">
<b-form-datepicker v-model="resubmissionDate" :min="now"></b-form-datepicker>
<time-picker v-model="resubmissionTime"></time-picker>
</b-form-group>
<b-tabs content-class="mt-3" v-model="tabindex">
<b-tab active>
<template #title>
<span id="message-tab-title">{{ $t('moderator.message') }}</span>
<b-tooltip target="message-tab-title" triggers="hover">
{{ $t('moderator.message-tooltip') }}
</b-tooltip>
</template>
<b-form-textarea
id="textarea"
v-model="form.text"
@ -20,7 +26,27 @@
rows="3"
></b-form-textarea>
</b-tab>
<b-tab :title="$t('moderator.memo')">
<b-tab>
<template #title>
<span id="notice-tab-title">{{ $t('moderator.notice') }}</span>
<b-tooltip target="notice-tab-title" triggers="hover">
{{ $t('moderator.notice-tooltip') }}
</b-tooltip>
</template>
<b-form-textarea
id="textarea"
v-model="form.text"
:placeholder="$t('contributionLink.memo')"
rows="3"
></b-form-textarea>
</b-tab>
<b-tab>
<template #title>
<span id="memo-tab-title">{{ $t('moderator.memo') }}</span>
<b-tooltip target="memo-tab-title" triggers="hover">
{{ $t('moderator.memo-tooltip') }}
</b-tooltip>
</template>
<b-form-textarea
id="textarea"
v-model="form.memo"
@ -33,37 +59,15 @@
<b-col>
<b-button type="reset" variant="danger">{{ $t('form.cancel') }}</b-button>
</b-col>
<b-col class="text-center">
<b-button
type="button"
variant="warning"
class="text-black"
@click.prevent="enableMemo()"
data-test="submit-memo"
>
{{ $t('moderator.memo-modify') }}
</b-button>
<b-button
type="button"
variant="warning"
class="text-black"
:disabled="moderatorDisabled"
@click.prevent="onSubmit(messageType.MODERATOR)"
data-test="submit-moderator"
>
{{ $t('moderator.notice') }}
</b-button>
</b-col>
<b-col class="text-right">
<b-button
type="submit"
variant="primary"
:disabled="disabled"
@click.prevent="onSubmit(messageType.DIALOG)"
@click.prevent="onSubmit()"
data-test="submit-dialog"
>
{{ $t('form.submit') }}
{{ $t('save') }}
</b-button>
</b-col>
</b-row>
@ -94,6 +98,10 @@ export default {
type: Boolean,
required: true,
},
inputResubmissionDate: {
type: String,
required: false,
},
},
data() {
return {
@ -102,10 +110,16 @@ export default {
memo: this.contributionMemo,
},
loading: false,
resubmissionDate: null,
resubmissionTime: '00:00',
showResubmissionDate: false,
chatOrMemo: 0, // 0 = Chat, 1 = Memo
resubmissionDate: this.inputResubmissionDate,
resubmissionTime: this.inputResubmissionDate
? new Date(this.inputResubmissionDate).toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
})
: '00:00',
showResubmissionDate:
this.inputResubmissionDate !== undefined && this.inputResubmissionDate !== null,
tabindex: 0, // 0 = Chat, 1 = Notice, 2 = Memo
messageType: {
DIALOG: 'DIALOG',
MODERATOR: 'MODERATOR',
@ -120,68 +134,69 @@ export default {
formattedDate.setMinutes(parseInt(minutes))
return formattedDate
},
onSubmit(mType) {
onSubmit() {
this.loading = true
if (this.chatOrMemo === 0) {
this.$apollo
.mutate({
mutation: adminCreateContributionMessage,
variables: {
contributionId: this.contributionId,
message: this.form.text,
messageType: mType,
resubmissionAt: this.showResubmissionDate
? this.combineResubmissionDateAndTime().toString()
: null,
},
})
.then((result) => {
if (
this.hideResubmission &&
this.showResubmissionDate &&
this.combineResubmissionDateAndTime() > new Date()
) {
this.$emit('update-contributions')
let mutation
const variables = {
id: this.contributionId,
resubmissionAt: this.showResubmissionDate
? this.combineResubmissionDateAndTime().toString()
: null,
}
// update only resubmission date?
if (this.form.text === '' && this.form.memo === this.contributionMemo) {
mutation = adminUpdateContribution
}
// update tabindex 0 = dialog or 1 = moderator
else if (this.tabindex !== 2) {
mutation = adminCreateContributionMessage
variables.message = this.form.text
variables.messageType =
this.tabindex === 0 ? this.messageType.DIALOG : this.messageType.MODERATOR
// update contribution memo
} else {
mutation = adminUpdateContribution
variables.memo = this.form.memo
}
this.$apollo
.mutate({ mutation, variables })
.then((result) => {
if (
this.hideResubmission &&
this.showResubmissionDate &&
this.combineResubmissionDateAndTime() > new Date()
) {
this.$emit('update-contributions')
} else {
this.$emit('update-status', this.contributionId)
if (this.tabindex === 2) {
this.$emit('reload-contribution', this.contributionId)
} else {
this.$emit('get-list-contribution-messages', this.contributionId)
this.$emit('update-status', this.contributionId)
}
this.onReset()
this.toastSuccess(this.$t('message.request'))
this.loading = false
})
.catch((error) => {
this.toastError(error.message)
this.loading = false
})
} else {
this.$apollo
.mutate({
mutation: adminUpdateContribution,
variables: {
id: this.contributionId,
memo: this.form.memo,
},
})
.then((result) => {
this.$emit('get-list-contribution-messages', this.contributionId)
this.$emit('update-status', this.contributionId)
this.$emit('reload-contribution', this.contributionId)
this.toastSuccess(this.$t('message.request'))
this.loading = false
})
.catch((error) => {
this.toastError(error.message)
this.loading = false
})
}
}
this.onReset()
this.toastSuccess(this.$t('message.request'))
this.loading = false
})
.catch((error) => {
this.toastError(error.message)
this.loading = false
})
},
onReset(event) {
this.form.text = ''
this.form.memo = this.contributionMemo
this.showResubmissionDate = false
this.resubmissionDate = null
this.resubmissionTime = '00:00'
this.resubmissionDate = this.inputResubmissionDate
this.resubmissionTime = this.inputResubmissionDate
? new Date(this.inputResubmissionDate).toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
})
: '00:00'
this.showResubmissionDate =
this.inputResubmissionDate !== undefined && this.inputResubmissionDate !== null
},
enableMemo() {
this.chatOrMemo = 1
@ -199,6 +214,9 @@ export default {
moderatorDisabled() {
return this.form.text === '' || this.loading || this.chatOrMemo === 1
},
now() {
return new Date()
},
},
}
</script>

View File

@ -13,6 +13,7 @@
:contributionId="contributionId"
:contributionMemo="contributionMemo"
:hideResubmission="hideResubmission"
:inputResubmissionDate="resubmissionAt"
@get-list-contribution-messages="$apollo.queries.Messages.refetch()"
@update-status="updateStatus"
@reload-contribution="reloadContribution"
@ -53,6 +54,10 @@ export default {
type: Boolean,
required: true,
},
resubmissionAt: {
type: String,
required: false,
},
},
data() {
return {

View File

@ -112,6 +112,7 @@
:contributionStatus="row.item.status"
:contributionUserId="row.item.userId"
:contributionMemo="row.item.memo"
:resubmissionAt="row.item.resubmissionAt"
:hideResubmission="hideResubmission"
@update-status="updateStatus"
@reload-contribution="reloadContribution"
@ -160,6 +161,10 @@ export default {
type: Boolean,
required: true,
},
resubmissionAt: {
type: Date,
required: false,
},
},
methods: {
myself(item) {

View File

@ -42,6 +42,7 @@ export const adminListContributions = gql`
deletedBy
moderatorId
userId
resubmissionAt
}
}
}

View File

@ -1,8 +1,20 @@
import gql from 'graphql-tag'
export const adminUpdateContribution = gql`
mutation ($id: Int!, $amount: Decimal, $memo: String, $creationDate: String) {
adminUpdateContribution(id: $id, amount: $amount, memo: $memo, creationDate: $creationDate) {
mutation (
$id: Int!
$amount: Decimal
$memo: String
$creationDate: String
$resubmissionAt: String
) {
adminUpdateContribution(
id: $id
amount: $amount
memo: $memo
creationDate: $creationDate
resubmissionAt: $resubmissionAt
) {
amount
date
memo

View File

@ -22,6 +22,7 @@ export const getContribution = gql`
deletedBy
moderatorId
userId
resubmissionAt
}
}
`

View File

@ -115,10 +115,13 @@
"history": "Die Daten wurden geändert. Dies sind die alten Daten.",
"show-submission-form": "warten auf Erinnerung?",
"moderator": "Moderator",
"notice": "Moderator Notiz",
"memo": "Memo",
"memo-modify": "Memo bearbeiten",
"notice": "Notiz",
"notice-tooltip": "Die Notiz ist nur für Moderatoren sichtbar",
"memo": "Text ändern",
"memo-tooltip": "Den Beitragstext bearbeiten",
"memo-modified": "Memo vom Moderator bearbeitet",
"message": "Nachricht",
"message-tooltip": "Nachricht an Benutzer schreiben",
"request": "Diese Nachricht ist nur für die Moderatoren sichtbar!"
},
"name": "Name",

View File

@ -115,10 +115,13 @@
"history": "The data has been changed. This is the old data.",
"show-submission-form": "wait for reminder?",
"moderator": "Moderator",
"notice": "Moderator note",
"memo": "Memo",
"memo-modify": "Modify Memo",
"notice": "Note",
"notice-tooltip": "The note is only visible to moderators",
"memo": "Edit text",
"memo-tooltip": "Edit the text of the contribution",
"memo-modified": "Memo edited by moderator",
"message": "Message",
"message-tooltip": "Write message to user",
"request": "This message is only visible to the moderators!"
},
"name": "Name",

View File

@ -3,6 +3,8 @@ import { ArgsType, Field, Int, InputType } from 'type-graphql'
import { ContributionMessageType } from '@enum/ContributionMessageType'
import { isValidDateString } from '@/graphql/validator/DateString'
@InputType()
@ArgsType()
export class ContributionMessageArgs {
@ -17,4 +19,8 @@ export class ContributionMessageArgs {
@Field(() => ContributionMessageType, { defaultValue: ContributionMessageType.DIALOG })
@IsEnum(ContributionMessageType)
messageType: ContributionMessageType
@Field(() => String, { nullable: true })
@isValidDateString()
resubmissionAt?: string | null
}

View File

@ -25,6 +25,7 @@ export class Contribution {
this.updatedBy = contribution.updatedBy
this.moderatorId = contribution.moderatorId
this.userId = contribution.userId
this.resubmissionAt = contribution.resubmissionAt
}
@Field(() => Int)
@ -83,6 +84,9 @@ export class Contribution {
@Field(() => Int, { nullable: true })
userId: number | null
@Field(() => Date, { nullable: true })
resubmissionAt: Date | null
}
@ObjectType()

View File

@ -54,7 +54,9 @@ export class ContributionMessageResolver {
contribution,
)
}
await transactionalEntityManager.insert(ContributionMessage, contributionMessage)
if (contributionMessage) {
await transactionalEntityManager.insert(ContributionMessage, contributionMessage)
}
finalContribution = contribution
finalContributionMessage = contributionMessage
@ -154,8 +156,9 @@ export class ContributionMessageResolver {
contribution,
)
}
await transactionalEntityManager.insert(ContributionMessage, contributionMessage)
if (contributionMessage) {
await transactionalEntityManager.insert(ContributionMessage, contributionMessage)
}
finalContribution = contribution
finalContributionMessage = contributionMessage
},

View File

@ -187,10 +187,10 @@ export class ContributionResolver {
const { contribution, contributionMessage, availableCreationSums } =
await updateUnconfirmedContributionContext.run()
await getConnection().transaction(async (transactionalEntityManager: EntityManager) => {
await Promise.all([
transactionalEntityManager.save(contribution),
transactionalEntityManager.save(contributionMessage),
])
await transactionalEntityManager.save(contribution)
if (contributionMessage) {
await transactionalEntityManager.save(contributionMessage)
}
})
const user = getUser(context)
await EVENT_CONTRIBUTION_UPDATE(user, contribution, contributionArgs.amount)
@ -263,10 +263,11 @@ export class ContributionResolver {
const { contribution, contributionMessage, createdByUserChangedByModerator } =
await updateUnconfirmedContributionContext.run()
await getConnection().transaction(async (transactionalEntityManager: EntityManager) => {
await Promise.all([
transactionalEntityManager.save(contribution),
transactionalEntityManager.save(contributionMessage),
])
await transactionalEntityManager.save(contribution)
console.log('saved changed contribution: %o', JSON.stringify(contribution, null, 2))
if (contributionMessage) {
await transactionalEntityManager.save(contributionMessage)
}
})
const moderator = getUser(context)

View File

@ -12,6 +12,7 @@ import { LogError } from '@/server/LogError'
export abstract class AbstractUnconfirmedContributionRole {
private availableCreationSums?: Decimal[]
protected changed = true
private currentStep = 0
public constructor(
protected self: Contribution,
@ -56,17 +57,29 @@ export abstract class AbstractUnconfirmedContributionRole {
// third, actually update entity
protected abstract update(): void
protected wasUpdateAlreadyCalled(): boolean {
return this.currentStep > 3
}
// call all steps in order
public async checkAndUpdate(context: Context): Promise<void> {
if (!context.user || !context.role) {
throw new LogError('missing user or role on context')
}
this.currentStep = 1
this.checkAuthorization(context.user, context.role)
this.currentStep = 2
await this.validate(getClientTimezoneOffset(context))
this.currentStep = 3
this.update()
this.currentStep = 4
}
public createContributionMessage(): ContributionMessageBuilder {
public createContributionMessage(): ContributionMessageBuilder | undefined {
// must be called before call at update
if (this.wasUpdateAlreadyCalled()) {
throw new LogError('please call before call of checkAndUpdate')
}
const contributionMessageBuilder = new ContributionMessageBuilder()
return contributionMessageBuilder.setParentContribution(this.self).setHistoryType(this.self)
}

View File

@ -23,13 +23,31 @@ export class UnconfirmedContributionAdminRole extends AbstractUnconfirmedContrib
)
}
/**
*
* @returns true if memo, amount and creation date are not changed at all
*/
private isContributionChanging(): boolean {
if (this.wasUpdateAlreadyCalled()) {
throw new LogError('please call only before calling checkAndUpdate')
}
return (
(this.updateData.memo && this.self.memo !== this.updateData.memo) ||
(this.updatedAmount && this.self.amount !== this.updatedAmount) ||
+this.self.contributionDate !== +this.updatedCreationDate
)
}
protected update(): void {
if (this.isContributionChanging()) {
// set update fields only if actual contribution was changed, not only the status or resubmission date
this.self.updatedAt = new Date()
this.self.updatedBy = this.moderator.id
this.self.contributionStatus = ContributionStatus.PENDING
}
this.self.amount = this.updatedAmount
this.self.memo = this.updateData.memo ?? this.self.memo
this.self.contributionDate = this.updatedCreationDate
this.self.contributionStatus = ContributionStatus.PENDING
this.self.updatedAt = new Date()
this.self.updatedBy = this.moderator.id
if (this.updateData.resubmissionAt) {
this.self.resubmissionAt = new Date(this.updateData.resubmissionAt)
} else {
@ -43,24 +61,40 @@ export class UnconfirmedContributionAdminRole extends AbstractUnconfirmedContrib
!role.hasRight(RIGHTS.MODERATOR_UPDATE_CONTRIBUTION_MEMO) &&
this.self.moderatorId === null
) {
throw new LogError('An admin is not allowed to update an user contribution')
throw new LogError("The Moderator hasn't the right MODERATOR_UPDATE_CONTRIBUTION_MEMO")
}
return this
}
protected async validate(clientTimezoneOffset: number): Promise<void> {
await super.validate(clientTimezoneOffset)
const newResubmissionDate = this.updateData.resubmissionAt
? new Date(this.updateData.resubmissionAt)
: null
const resubmissionNotChanged =
this.self.resubmissionAt !== null &&
newResubmissionDate !== null &&
+this.self.resubmissionAt === +newResubmissionDate
// creation date is currently not changeable
if (
this.self.memo === this.updateData.memo &&
this.self.amount === this.updatedAmount &&
this.self.contributionDate.getTime() === new Date(this.updatedCreationDate).getTime()
this.isContributionChanging() &&
((this.self.resubmissionAt === null && newResubmissionDate === null) ||
resubmissionNotChanged)
) {
throw new LogError("the contribution wasn't changed at all")
}
}
public createContributionMessage(): ContributionMessageBuilder {
return super.createContributionMessage().setIsModerator(true)
public createContributionMessage(): ContributionMessageBuilder | undefined {
if (!this.isContributionChanging()) {
return
}
const builder = super.createContributionMessage()
if (builder) {
return builder.setIsModerator(true)
}
}
}

View File

@ -20,9 +20,15 @@ export class UnconfirmedContributionAdminAddMessageRole extends AbstractUnconfir
if (this.updateData.messageType !== ContributionMessageType.MODERATOR) {
newStatus = ContributionStatus.IN_PROGRESS
}
if (this.self.contributionStatus !== newStatus || this.self.resubmissionAt != null) {
const resubmissionDate: Date | null = this.updateData.resubmissionAt
? new Date(this.updateData.resubmissionAt)
: null
if (
this.self.contributionStatus !== newStatus ||
this.self.resubmissionAt !== resubmissionDate
) {
this.self.contributionStatus = newStatus
this.self.resubmissionAt = null
this.self.resubmissionAt = resubmissionDate
} else {
this.changed = false
}
@ -46,10 +52,12 @@ export class UnconfirmedContributionAdminAddMessageRole extends AbstractUnconfir
await super.validate(clientTimezoneOffset)
}
public createContributionMessage(): ContributionMessageBuilder {
return super
.createContributionMessage()
.setIsModerator(true)
.setMessageAndType(this.updateData.message, this.updateData.messageType)
public createContributionMessage(): ContributionMessageBuilder | undefined {
const builder = super.createContributionMessage()
if (builder) {
return builder
.setIsModerator(true)
.setMessageAndType(this.updateData.message, this.updateData.messageType)
}
}
}

View File

@ -58,7 +58,10 @@ export class UnconfirmedContributionUserRole extends AbstractUnconfirmedContribu
}
}
public createContributionMessage(): ContributionMessageBuilder {
return super.createContributionMessage().setIsModerator(false)
public createContributionMessage(): ContributionMessageBuilder | undefined {
const builder = super.createContributionMessage()
if (builder) {
return builder.setIsModerator(false)
}
}
}

View File

@ -42,10 +42,10 @@ export class UnconfirmedContributionUserAddMessageRole extends AbstractUnconfirm
return this
}
public createContributionMessage(): ContributionMessageBuilder {
return super
.createContributionMessage()
.setIsModerator(false)
.setDialogType(this.updateData.message)
public createContributionMessage(): ContributionMessageBuilder | undefined {
const builder = super.createContributionMessage()
if (builder) {
return builder.setIsModerator(false).setDialogType(this.updateData.message)
}
}
}

View File

@ -39,7 +39,7 @@ export class UpdateUnconfirmedContributionContext {
relations?: FindOptionsRelations<Contribution>,
): Promise<{
contribution: Contribution
contributionMessage: ContributionMessage
contributionMessage: ContributionMessage | undefined
availableCreationSums: Decimal[]
createdByUserChangedByModerator: boolean
contributionChanged: boolean
@ -73,6 +73,7 @@ export class UpdateUnconfirmedContributionContext {
this.input,
this.context.user,
)
if (unconfirmedContributionRole.isCreatedFromUser()) {
createdByUserChangedByModerator = true
}
@ -92,16 +93,20 @@ export class UpdateUnconfirmedContributionContext {
if (!unconfirmedContributionRole) {
throw new LogError("don't recognize input type, maybe not implemented yet?")
}
const contributionMessageBuilder = unconfirmedContributionRole.createContributionMessage()
if (contributionMessageBuilder) {
contributionMessageBuilder.setUser(this.context.user)
}
// run steps
// all possible cases not to be true are thrown in the next function
await unconfirmedContributionRole.checkAndUpdate(this.context)
return {
contribution: contributionToUpdate,
contributionMessage: unconfirmedContributionRole
.createContributionMessage()
.setUser(this.context.user)
.build(),
contributionMessage: contributionMessageBuilder
? contributionMessageBuilder.build()
: undefined,
availableCreationSums: unconfirmedContributionRole.getAvailableCreationSums(),
createdByUserChangedByModerator,
contributionChanged: unconfirmedContributionRole.isChanged(),