todo-helden/models/Reward.js
Michi 0ebe7fa13d
Some checks failed
🚀 Continuous Integration / 🔧 Backend Tests (18.x) (push) Has been cancelled
🚀 Continuous Integration / 🔧 Backend Tests (20.x) (push) Has been cancelled
🚀 Continuous Integration / 🎨 Frontend Tests (18.x) (push) Has been cancelled
🚀 Continuous Integration / 🎨 Frontend Tests (20.x) (push) Has been cancelled
🚀 Continuous Integration / 🔍 Code Quality (push) Has been cancelled
🚀 Continuous Integration / 🔒 Security Checks (push) Has been cancelled
🚀 Continuous Integration / 🎨 Theme Tests (push) Has been cancelled
🚀 Continuous Integration / ♿ Accessibility Tests (push) Has been cancelled
🚀 Continuous Integration / 📱 Cross-Browser Tests (push) Has been cancelled
🚀 Continuous Integration / 🏗️ Build Tests (push) Has been cancelled
🚀 Continuous Integration / 📊 Performance Tests (push) Has been cancelled
🚀 Continuous Integration / 🎯 Integration Tests (push) Has been cancelled
🚀 Continuous Integration / ✅ All Tests Passed (push) Has been cancelled
Initial commit: ToDo Kids v1.0.0
2025-08-04 15:46:08 +02:00

359 lines
8.2 KiB
JavaScript

const mongoose = require('mongoose');
// Schema für Belohnungen
const rewardSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
maxlength: 100
},
description: {
type: String,
trim: true,
maxlength: 500
},
icon: {
type: String,
default: 'gift',
enum: [
'gift', 'tv', 'game', 'movie', 'ice-cream', 'toy', 'book',
'outing', 'money', 'treat', 'activity', 'privilege', 'special',
'electronics', 'sports', 'art', 'music', 'food', 'other'
]
},
cost: {
type: Number,
required: true,
min: 1,
max: 10000
},
category: {
type: String,
enum: [
'screen-time', 'treats', 'toys', 'activities', 'outings',
'privileges', 'money', 'special', 'electronics', 'books',
'sports', 'art', 'other'
],
default: 'treats'
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
availableFor: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Child'
}], // Leer = für alle Kinder verfügbar
ageRestriction: {
minAge: {
type: Number,
min: 3,
max: 18
},
maxAge: {
type: Number,
min: 3,
max: 18
}
},
availability: {
isLimited: {
type: Boolean,
default: false
},
totalQuantity: {
type: Number,
min: 1,
required: function() { return this.availability.isLimited; }
},
remainingQuantity: {
type: Number,
min: 0,
required: function() { return this.availability.isLimited; }
},
resetPeriod: {
type: String,
enum: ['daily', 'weekly', 'monthly', 'never'],
default: 'never'
},
lastReset: Date
},
timeRestrictions: {
validFrom: Date,
validUntil: Date,
daysOfWeek: [{
type: Number,
min: 0,
max: 6 // 0 = Sonntag, 6 = Samstag
}],
timeOfDay: {
startTime: String, // Format: "HH:MM"
endTime: String // Format: "HH:MM"
}
},
requiresApproval: {
type: Boolean,
default: true
},
isInstant: {
type: Boolean,
default: false // true = sofort verfügbar, false = muss genehmigt werden
},
tags: [{
type: String,
trim: true,
maxlength: 20
}],
image: {
type: String,
default: null
},
instructions: {
type: String,
trim: true,
maxlength: 1000
},
isActive: {
type: Boolean,
default: true
},
isPopular: {
type: Boolean,
default: false
},
sortOrder: {
type: Number,
default: 0
},
stats: {
timesRedeemed: {
type: Number,
default: 0
},
totalPointsSpent: {
type: Number,
default: 0
},
lastRedeemed: Date
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// Schema für Belohnungs-Anfragen
const rewardRequestSchema = new mongoose.Schema({
reward: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Reward',
required: true
},
child: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Child',
required: true
},
pointsSpent: {
type: Number,
required: true,
min: 1
},
status: {
type: String,
enum: ['pending', 'approved', 'rejected', 'redeemed'],
default: 'pending'
},
requestedAt: {
type: Date,
default: Date.now
},
processedAt: Date,
processedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
redeemedAt: Date,
rejectionReason: {
type: String,
trim: true,
maxlength: 200
},
notes: {
type: String,
trim: true,
maxlength: 500
}
});
// Indizes für bessere Performance
rewardSchema.index({ createdBy: 1, isActive: 1 });
rewardSchema.index({ cost: 1, isActive: 1 });
rewardSchema.index({ category: 1, isActive: 1 });
rewardSchema.index({ isPopular: -1, sortOrder: 1 });
rewardRequestSchema.index({ child: 1, status: 1, requestedAt: -1 });
rewardRequestSchema.index({ status: 1, requestedAt: -1 });
// Middleware: updatedAt automatisch setzen
rewardSchema.pre('save', function(next) {
this.updatedAt = new Date();
next();
});
// Methode: Verfügbarkeit prüfen
rewardSchema.methods.isAvailableFor = function(child) {
// Altersrestriktion prüfen
if (this.ageRestriction.minAge && child.age < this.ageRestriction.minAge) {
return false;
}
if (this.ageRestriction.maxAge && child.age > this.ageRestriction.maxAge) {
return false;
}
// Verfügbarkeit für spezifische Kinder prüfen
if (this.availableFor.length > 0 && !this.availableFor.includes(child._id)) {
return false;
}
// Mengenbegrenzung prüfen
if (this.availability.isLimited && this.availability.remainingQuantity <= 0) {
return false;
}
// Zeitrestriktionen prüfen
const now = new Date();
if (this.timeRestrictions.validFrom && now < this.timeRestrictions.validFrom) {
return false;
}
if (this.timeRestrictions.validUntil && now > this.timeRestrictions.validUntil) {
return false;
}
return this.isActive;
};
// Methode: Belohnung einlösen
rewardSchema.methods.redeem = async function(childId) {
const Child = mongoose.model('Child');
const child = await Child.findById(childId);
if (!child) {
throw new Error('Kind nicht gefunden');
}
if (!this.isAvailableFor(child)) {
throw new Error('Belohnung nicht verfügbar für dieses Kind');
}
if (child.points.available < this.cost) {
throw new Error('Nicht genügend Punkte verfügbar');
}
// Punkte abziehen
await child.spendPoints(this.cost);
// Verfügbare Menge reduzieren
if (this.availability.isLimited) {
this.availability.remainingQuantity -= 1;
}
// Statistiken aktualisieren
this.stats.timesRedeemed += 1;
this.stats.totalPointsSpent += this.cost;
this.stats.lastRedeemed = new Date();
await this.save();
// Belohnungs-Anfrage erstellen (falls Genehmigung erforderlich)
if (this.requiresApproval) {
const RewardRequest = mongoose.model('RewardRequest');
const request = new RewardRequest({
reward: this._id,
child: childId,
pointsSpent: this.cost,
status: 'pending'
});
await request.save();
return request;
} else {
// Sofortige Einlösung
const RewardRequest = mongoose.model('RewardRequest');
const request = new RewardRequest({
reward: this._id,
child: childId,
pointsSpent: this.cost,
status: 'redeemed',
redeemedAt: new Date()
});
await request.save();
return request;
}
};
// Statische Methode: Verfügbare Belohnungen für ein Kind
rewardSchema.statics.findAvailableFor = function(child) {
const query = {
isActive: true,
$or: [
{ availableFor: { $size: 0 } }, // Für alle verfügbar
{ availableFor: child._id } // Spezifisch für dieses Kind
]
};
// Altersrestriktionen
if (child.age) {
query.$and = [
{ $or: [{ 'ageRestriction.minAge': { $exists: false } }, { 'ageRestriction.minAge': { $lte: child.age } }] },
{ $or: [{ 'ageRestriction.maxAge': { $exists: false } }, { 'ageRestriction.maxAge': { $gte: child.age } }] }
];
}
return this.find(query).sort({ isPopular: -1, sortOrder: 1, cost: 1 });
};
// Methoden für RewardRequest
rewardRequestSchema.methods.approve = function(approvedByUserId) {
this.status = 'approved';
this.processedAt = new Date();
this.processedBy = approvedByUserId;
return this.save();
};
rewardRequestSchema.methods.reject = function(reason, rejectedByUserId) {
this.status = 'rejected';
this.rejectionReason = reason;
this.processedAt = new Date();
this.processedBy = rejectedByUserId;
// Punkte zurückgeben
return this.refundPoints();
};
rewardRequestSchema.methods.markRedeemed = function() {
this.status = 'redeemed';
this.redeemedAt = new Date();
return this.save();
};
rewardRequestSchema.methods.refundPoints = async function() {
const Child = mongoose.model('Child');
const child = await Child.findById(this.child);
if (child) {
child.points.available += this.pointsSpent;
child.points.spent -= this.pointsSpent;
await child.save();
}
return this.save();
};
const Reward = mongoose.model('Reward', rewardSchema);
const RewardRequest = mongoose.model('RewardRequest', rewardRequestSchema);
module.exports = { Reward, RewardRequest };