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 };