diff --git a/backend/models/associations.js b/backend/models/associations.js index 38f0487..356bb72 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -421,6 +421,13 @@ export default function setupAssociations() { DaySell.belongsTo(FalukantUser, { foreignKey: 'sellerId', as: 'user' }); FalukantUser.hasMany(DaySell, { foreignKey: 'sellerId', as: 'daySells' }); + // Produkt-Preishistorie (Zeitreihe für Preiskurven) + ProductPriceHistory.belongsTo(ProductType, { foreignKey: 'productId', as: 'productType' }); + ProductType.hasMany(ProductPriceHistory, { foreignKey: 'productId', as: 'priceHistory' }); + + ProductPriceHistory.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' }); + RegionData.hasMany(ProductPriceHistory, { foreignKey: 'regionId', as: 'productPriceHistory' }); + Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' }); FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' }); diff --git a/backend/models/falukant/log/product_price_history.js b/backend/models/falukant/log/product_price_history.js new file mode 100644 index 0000000..1575fcc --- /dev/null +++ b/backend/models/falukant/log/product_price_history.js @@ -0,0 +1,44 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +/** + * Preishistorie pro Produkt und Region (Zeitreihe für Preis-Graphen). + * Aktuell wird diese Tabelle noch nicht befüllt; sie dient nur als Grundlage. + */ +class ProductPriceHistory extends Model { } + +ProductPriceHistory.init({ + productId: { + type: DataTypes.INTEGER, + allowNull: false + }, + regionId: { + type: DataTypes.INTEGER, + allowNull: false + }, + price: { + type: DataTypes.DECIMAL(12, 2), + allowNull: false + }, + recordedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: sequelize.literal('CURRENT_TIMESTAMP') + } +}, { + sequelize, + modelName: 'ProductPriceHistory', + tableName: 'product_price_history', + schema: 'falukant_log', + timestamps: false, + underscored: true, + indexes: [ + { + name: 'product_price_history_product_region_recorded_idx', + fields: ['product_id', 'region_id', 'recorded_at'] + } + ] +}); + +export default ProductPriceHistory; + diff --git a/backend/models/index.js b/backend/models/index.js index 453dfed..78d4056 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -89,6 +89,7 @@ import Learning from './falukant/data/learning.js'; import Credit from './falukant/data/credit.js'; import DebtorsPrism from './falukant/data/debtors_prism.js'; import HealthActivity from './falukant/log/health_activity.js'; +import ProductPriceHistory from './falukant/log/product_price_history.js'; // — Match3 Minigame — import Match3Campaign from './match3/campaign.js'; @@ -239,6 +240,7 @@ const models = { Credit, DebtorsPrism, HealthActivity, + ProductPriceHistory, RegionDistance, VehicleType, Vehicle, diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index f154331..6ced5cf 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -2901,9 +2901,9 @@ class FalukantService extends BaseService { } async getGifts(hashedUserId) { - // 1) Mein User & Character + // 1) Mein aktiver Falukant-User & dessen aktueller Charakter const user = await this.getFalukantUserByHashedId(hashedUserId); - const myChar = await FalukantCharacter.findOne({ where: { userId: user.id } }); + const myChar = user.character; if (!myChar) throw new Error('Character not found'); // 2) Beziehung finden und „anderen“ Character bestimmen @@ -3520,12 +3520,10 @@ class FalukantService extends BaseService { async baptise(hashedUserId, childId, firstName) { try { - const falukantUser = await getFalukantUserOrFail(hashedUserId); - const parentCharacter = await FalukantCharacter.findOne({ - where: { - userId: falukantUser.id, - }, - }); + // Nutze den aktuell aktiven Charakter (wie in getFalukantUserByHashedId definiert), + // statt „irgendeinen“ Charakter des Users per userId zu suchen. + const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); + const parentCharacter = falukantUser.character; if (!parentCharacter) { throw new Error('Parent character not found'); } diff --git a/backend/utils/syncDatabase.js b/backend/utils/syncDatabase.js index 3a3dfd3..3ee14d9 100644 --- a/backend/utils/syncDatabase.js +++ b/backend/utils/syncDatabase.js @@ -539,6 +539,27 @@ const syncDatabase = async () => { console.warn('⚠️ relationship_change_log/Trigger konnten nicht sichergestellt werden:', e?.message || e); } + // Preishistorie für Produkte (Zeitreihe) – nur Schema/Struktur, noch ohne Logik + console.log("Ensuring falukant_log.product_price_history exists..."); + try { + await sequelize.query(` + CREATE TABLE IF NOT EXISTS falukant_log.product_price_history ( + id serial PRIMARY KEY, + product_id integer NOT NULL, + region_id integer NOT NULL, + price numeric(12,2) NOT NULL, + recorded_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + `); + await sequelize.query(` + CREATE INDEX IF NOT EXISTS product_price_history_product_region_recorded_idx + ON falukant_log.product_price_history (product_id, region_id, recorded_at); + `); + console.log("✅ product_price_history ist vorhanden."); + } catch (e) { + console.warn('⚠️ product_price_history konnte nicht sichergestellt werden:', e?.message || e); + } + // Vorab: Stelle kritische Spalten sicher, damit Index-Erstellung nicht fehlschlägt console.log("Pre-ensure Taxi columns (traffic_light) ..."); try {