Füge Funktion zum Abbrechen der Werbung hinzu: Implementiere cancelWooing in FalukantService und FalukantController, aktualisiere FamilyView für die Benutzeroberfläche und verbessere die Fehlermeldungen bei vorzeitigen Abbrüchen.

This commit is contained in:
Torsten Schulz (local)
2026-01-28 11:53:34 +01:00
parent 16f3d1a320
commit cbff7c130c
8 changed files with 119 additions and 7 deletions

View File

@@ -94,6 +94,16 @@ class FalukantController {
}); });
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId)); 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.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) => { this.getGifts = this._wrapWithUser((userId) => {
console.log('🔍 getGifts called with userId:', userId); console.log('🔍 getGifts called with userId:', userId);
return this.service.getGifts(userId); return this.service.getGifts(userId);

View File

@@ -38,6 +38,7 @@ router.get('/director/:branchId', falukantController.getDirectorForBranch);
router.get('/directors', falukantController.getAllDirectors); router.get('/directors', falukantController.getAllDirectors);
router.post('/directors', falukantController.updateDirector); router.post('/directors', falukantController.updateDirector);
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal); router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
router.post('/family/cancel-wooing', falukantController.cancelWooing);
router.post('/family/set-heir', falukantController.setHeir); router.post('/family/set-heir', falukantController.setHeir);
router.get('/family/gifts', falukantController.getGifts); router.get('/family/gifts', falukantController.getGifts);
router.get('/family/children', falukantController.getChildren); router.get('/family/children', falukantController.getChildren);

View File

@@ -2733,6 +2733,33 @@ class FalukantService extends BaseService {
return { success: true, message: 'Marriage proposal accepted' }; 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) { async getGifts(hashedUserId) {
// 1) Mein User & Character // 1) Mein User & Character
const user = await this.getFalukantUserByHashedId(hashedUserId); const user = await this.getFalukantUserByHashedId(hashedUserId);

View File

@@ -457,6 +457,10 @@
"wooing": { "wooing": {
"gifts": "Werbegeschenke", "gifts": "Werbegeschenke",
"sendGift": "Werbegeschenk senden", "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", "gift": "Geschenk",
"value": "Kosten", "value": "Kosten",
"effect": "Wirkung" "effect": "Wirkung"

View File

@@ -29,7 +29,9 @@
"datetimelong": "dd.MM.yyyy HH:mm:ss", "datetimelong": "dd.MM.yyyy HH:mm:ss",
"loading": "Lädt...", "loading": "Lädt...",
"back": "Zurück", "back": "Zurück",
"cancel": "Abbrechen" "cancel": "Abbrechen",
"yes": "Ja",
"no": "Nein"
}, },
"OK": "Ok", "OK": "Ok",
"Cancel": "Abbrechen", "Cancel": "Abbrechen",

View File

@@ -303,6 +303,24 @@
"taxPercent": "Tax %" "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"
} }
} }
} }

View File

@@ -1,20 +1,52 @@
{ {
"welcome": "Welcome to YourPart", "welcome": "Welcome to YourPart",
"imprint": { "imprint": {
"title": "Imprint" "title": "Imprint",
"button": "Imprint"
}, },
"dataPrivacy": { "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": { "general": {
"loading": "Loading...", "loading": "Loading...",
"back": "Back", "back": "Back",
"cancel": "Cancel", "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": { "message": {
"close": "Close" "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": { "common": {
"edit": "Edit", "edit": "Edit",
"delete": "Delete", "delete": "Delete",

View File

@@ -82,9 +82,9 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div> <div class="wooing-actions">
<button @click="sendGift" class="button">{{ $t('falukant.family.spouse.wooing.sendGift') <button @click="sendGift" class="button">{{ $t('falukant.family.spouse.wooing.sendGift') }}</button>
}}</button> <button @click="cancelWooing" class="button button-secondary">{{ $t('falukant.family.spouse.wooing.cancel') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -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() { async sendGift() {
if (!this.selectedGiftId) { if (!this.selectedGiftId) {
this.$root.$refs.errorDialog.open(`tr:falukant.family.sendgift.error.nogiftselected`); this.$root.$refs.errorDialog.open(`tr:falukant.family.sendgift.error.nogiftselected`);