From cbff7c130cb2c09b596018970e0c39cab48e5338 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 28 Jan 2026 11:53:34 +0100 Subject: [PATCH] =?UTF-8?q?F=C3=BCge=20Funktion=20zum=20Abbrechen=20der=20?= =?UTF-8?q?Werbung=20hinzu:=20Implementiere=20cancelWooing=20in=20Falukant?= =?UTF-8?q?Service=20und=20FalukantController,=20aktualisiere=20FamilyView?= =?UTF-8?q?=20f=C3=BCr=20die=20Benutzeroberfl=C3=A4che=20und=20verbessere?= =?UTF-8?q?=20die=20Fehlermeldungen=20bei=20vorzeitigen=20Abbr=C3=BCchen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/controllers/falukantController.js | 10 ++++++ backend/routers/falukantRouter.js | 1 + backend/services/falukantService.js | 27 +++++++++++++++ frontend/src/i18n/locales/de/falukant.json | 4 +++ frontend/src/i18n/locales/de/general.json | 4 ++- frontend/src/i18n/locales/en/falukant.json | 18 ++++++++++ frontend/src/i18n/locales/en/general.json | 38 ++++++++++++++++++++-- frontend/src/views/falukant/FamilyView.vue | 24 ++++++++++++-- 8 files changed, 119 insertions(+), 7 deletions(-) diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index a2d3e7d..e7db3d4 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -94,6 +94,16 @@ class FalukantController { }); this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId)); this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId)); + this.cancelWooing = this._wrapWithUser(async (userId) => { + try { + return await this.service.cancelWooing(userId); + } catch (e) { + if (e && e.name === 'PreconditionError' && e.message === 'cancelTooSoon') { + throw { status: 412, message: 'cancelTooSoon', retryAt: e.meta?.retryAt }; + } + throw e; + } + }, { successStatus: 202 }); this.getGifts = this._wrapWithUser((userId) => { console.log('🔍 getGifts called with userId:', userId); return this.service.getGifts(userId); diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 1d36092..cf3f967 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -38,6 +38,7 @@ router.get('/director/:branchId', falukantController.getDirectorForBranch); router.get('/directors', falukantController.getAllDirectors); router.post('/directors', falukantController.updateDirector); router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal); +router.post('/family/cancel-wooing', falukantController.cancelWooing); router.post('/family/set-heir', falukantController.setHeir); router.get('/family/gifts', falukantController.getGifts); router.get('/family/children', falukantController.getChildren); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 7ba69fd..5bcb743 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -2733,6 +2733,33 @@ class FalukantService extends BaseService { return { success: true, message: 'Marriage proposal accepted' }; } + async cancelWooing(hashedUserId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + if (!user || !user.character) { + throw new Error('User or character not found'); + } + const relation = await Relationship.findOne({ + where: { character1Id: user.character.id }, + include: [{ + model: RelationshipType, + as: 'relationshipType', + where: { tr: 'wooing' } + }] + }); + if (!relation) { + throw new Error('noWooing'); + } + // Require at least 24h between starting wooing (relation.createdAt) and cancelling + const earliestCancel = new Date(relation.createdAt.getTime() + 24 * 3600 * 1000); + if (Date.now() < earliestCancel.getTime()) { + const err = new PreconditionError('cancelTooSoon'); + err.meta = { retryAt: earliestCancel.toISOString() }; + throw err; + } + await relation.destroy(); + return { success: true }; + } + async getGifts(hashedUserId) { // 1) Mein User & Character const user = await this.getFalukantUserByHashedId(hashedUserId); diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index e1e3ca7..bf8606b 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -457,6 +457,10 @@ "wooing": { "gifts": "Werbegeschenke", "sendGift": "Werbegeschenk senden", + "cancel": "Werbung abbrechen", + "cancelSuccess": "Die Werbung wurde abgebrochen.", + "cancelError": "Die Werbung konnte nicht abgebrochen werden.", + "cancelTooSoon": "Du kannst die Werbung erst nach 24 Stunden abbrechen.", "gift": "Geschenk", "value": "Kosten", "effect": "Wirkung" diff --git a/frontend/src/i18n/locales/de/general.json b/frontend/src/i18n/locales/de/general.json index 2a33163..28782c7 100644 --- a/frontend/src/i18n/locales/de/general.json +++ b/frontend/src/i18n/locales/de/general.json @@ -29,7 +29,9 @@ "datetimelong": "dd.MM.yyyy HH:mm:ss", "loading": "Lädt...", "back": "Zurück", - "cancel": "Abbrechen" + "cancel": "Abbrechen", + "yes": "Ja", + "no": "Nein" }, "OK": "Ok", "Cancel": "Abbrechen", diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 9d35108..9a2d59f 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -303,6 +303,24 @@ "taxPercent": "Tax %" } } + }, + "spouse": { + "wooing": { + "cancel": "Cancel wooing", + "cancelSuccess": "Wooing has been cancelled.", + "cancelError": "Wooing could not be cancelled.", + "cancelTooSoon": "You can only cancel wooing after 24 hours." + } + }, + "sendgift": { + "error": { + "nogiftselected": "Please select a gift.", + "generic": "An unknown error occurred.", + "tooOften": "You can't send gifts that often.", + "insufficientFunds": "You do not have enough money." + }, + "success": "The gift has been given.", + "nextGiftAt": "Next gift from" } } } diff --git a/frontend/src/i18n/locales/en/general.json b/frontend/src/i18n/locales/en/general.json index 1028dbf..3110127 100644 --- a/frontend/src/i18n/locales/en/general.json +++ b/frontend/src/i18n/locales/en/general.json @@ -1,20 +1,52 @@ { "welcome": "Welcome to YourPart", "imprint": { - "title": "Imprint" + "title": "Imprint", + "button": "Imprint" }, "dataPrivacy": { - "title": "Data Privacy Policy" + "title": "Data Privacy Policy", + "button": "Data Privacy Policy" }, + "contact": { + "title": "Contact", + "button": "Contact" + }, + "error-title": "Error", + "warning-title": "Warning", + "info-title": "Information", "general": { "loading": "Loading...", "back": "Back", "cancel": "Cancel", - "datetimelong": "dd.MM.yyyy HH:mm:ss" + "datetimelong": "dd.MM.yyyy HH:mm:ss", + "yes": "Yes", + "no": "No" }, + "OK": "Ok", + "Cancel": "Cancel", + "yes": "Yes", + "no": "No", "message": { "close": "Close" }, + "dialog": { + "contact": { + "email": "Email address", + "name": "Name", + "message": "Your message to us", + "accept": "Your email address will be temporarily stored in our system. After your request has been processed, the email address will be deleted again.", + "acceptdatasave": "I agree to the temporary storage of my email address.", + "accept2": "Without this consent we unfortunately cannot reply to you." + } + }, + "gender": { + "male": "Male", + "female": "Female", + "transmale": "Trans man", + "transfemale": "Trans woman", + "nonbinary": "Non-binary" + }, "common": { "edit": "Edit", "delete": "Delete", diff --git a/frontend/src/views/falukant/FamilyView.vue b/frontend/src/views/falukant/FamilyView.vue index 8aa7e97..c762129 100644 --- a/frontend/src/views/falukant/FamilyView.vue +++ b/frontend/src/views/falukant/FamilyView.vue @@ -82,9 +82,9 @@ -
- +
+ +
@@ -341,6 +341,24 @@ export default { } }, + async cancelWooing() { + try { + await apiClient.post('/api/falukant/family/cancel-wooing'); + await this.loadFamilyData(); + this.$root.$refs.messageDialog?.open('tr:falukant.family.spouse.wooing.cancelSuccess'); + } catch (error) { + console.error('Error cancelling wooing:', error); + if (error?.response?.status === 412) { + const retryAtIso = error.response?.data?.retryAt; + const retryStr = retryAtIso ? new Date(retryAtIso).toLocaleString(navigator.language, { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : ''; + const msg = this.$t('falukant.family.spouse.wooing.cancelTooSoon'); + this.$root.$refs.errorDialog?.open(retryStr ? `${msg} — ${this.$t('falukant.family.sendgift.nextGiftAt')}: ${retryStr}` : msg); + } else { + this.$root.$refs.errorDialog?.open('tr:falukant.family.spouse.wooing.cancelError'); + } + } + }, + async sendGift() { if (!this.selectedGiftId) { this.$root.$refs.errorDialog.open(`tr:falukant.family.sendgift.error.nogiftselected`);