diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index b477085..7da6b0c 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -9,6 +9,18 @@ class FalukantController { this.getInfo = this.getInfo.bind(this); this.getInventory = this.getInventory.bind(this); this.sellProduct = this.sellProduct.bind(this); + this.sellAllProducts = this.sellAllProducts.bind(this); + this.moneyHistory = this.moneyHistory.bind(this); + this.getStorage = this.getStorage.bind(this); + this.buyStorage = this.buyStorage.bind(this); + this.sellStorage = this.sellStorage.bind(this); + this.getStockTypes = this.getStockTypes.bind(this); + this.getStockOverview = this.getStockOverview.bind(this); + this.getAllProductions = this.getAllProductions.bind(this); + this.getDirectorProposals = this.getDirectorProposals.bind(this); + this.convertProposalToDirector = this.convertProposalToDirector.bind(this); + this.getDirectorForBranch = this.getDirectorForBranch.bind(this); + this.setSetting = this.setSetting.bind(this); } async getUser(req, res) { @@ -118,6 +130,7 @@ class FalukantController { } async createStock(req, res) { + console.log('build stock'); try { const { userid: hashedUserId } = req.headers; const { branchId, stockTypeId, stockSize } = req.body; @@ -159,6 +172,146 @@ class FalukantController { res.status(500).json({ error: error.message }); } } + + async sellAllProducts(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.body; + const result = await FalukantService.sellAllProducts(hashedUserId, branchId); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async moneyHistory(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { page, filter } = req.body; + if (!page) { + page = 1; + } + const result = await FalukantService.moneyHistory(hashedUserId, page, filter); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getStorage(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.params; + const result = await FalukantService.getStorage(hashedUserId, branchId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message}); + console.log(error); + } + } + + async buyStorage(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId, amount, stockTypeId } = req.body; + const result = await FalukantService.buyStorage(hashedUserId, branchId, amount, stockTypeId); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message}); + console.log(error); + } + } + + async sellStorage(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId, amount, stockTypeId } = req.body; + const result = await FalukantService.sellStorage(hashedUserId, branchId, amount, stockTypeId); + res.status(202).json(result); + } catch (error) { + res.status(500).json({ error: error.message}); + console.log(error); + } + } + + async getStockTypes(req, res) { + console.log('load stock'); + try { + const result = await FalukantService.getStockTypes(); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + + async getStockOverview(req, res) { + try { + const result = await FalukantService.getStockOverview(); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getAllProductions(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getAllProductions(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getDirectorProposals(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.body; + const result = await FalukantService.getDirectorProposals(hashedUserId, branchId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async convertProposalToDirector(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { proposalId } = req.body; + const result = await FalukantService.convertProposalToDirector(hashedUserId, proposalId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getDirectorForBranch(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.params; + const result = await FalukantService.getDirectorForBranch(hashedUserId, branchId); + if (!result) { + return res.status(404).json({ message: 'No director found for this branch' }); + } + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async setSetting(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId, directorId, settingKey, value } = req.body; + const result = await FalukantService.setSetting(hashedUserId, branchId, directorId, settingKey, value); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + console.log(error); + } + } + } export default FalukantController; diff --git a/backend/models/associations.js b/backend/models/associations.js index ee2c846..fd2fa09 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -44,8 +44,10 @@ import Branch from './falukant/data/branch.js'; import BranchType from './falukant/type/branch.js'; import Production from './falukant/data/production.js'; import Inventory from './falukant/data/inventory.js'; -import BuyableStock from './falukant/data/buayble_stock.js'; - +import BuyableStock from './falukant/data/buyable_stock.js'; +import MoneyFlow from './falukant/log/moneyflow.js'; +import Director from './falukant/data/director.js'; +import DirectorProposal from './falukant/data/director_proposal.js'; export default function setupAssociations() { // UserParam related associations @@ -251,4 +253,21 @@ export default function setupAssociations() { Branch.hasMany(FalukantStock, { foreignKey: 'branchId', as: 'stocks' }); FalukantStock.belongsTo(Branch, { foreignKey: 'branchId', as: 'branch' }); + MoneyFlow.belongsTo(FalukantUser, { foreignKey: 'falukantUserId', as: 'user' }); + FalukantUser.hasMany(MoneyFlow, { foreignKey: 'falukantUserId', as: 'flows' }); + + BuyableStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' }); + FalukantStockType.hasMany(BuyableStock, { foreignKey: 'stockTypeId', as: 'buyableStocks' }); + + Director.belongsTo(FalukantUser, { foreignKey: 'employerUserId', as: 'user' }); + FalukantUser.hasMany(Director, { foreignKey: 'employerUserId', as: 'directors' }); + + Director.belongsTo(FalukantCharacter, { foreignKey: 'directorCharacterId', as: 'character' }); + FalukantCharacter.hasMany(Director, { foreignKey: 'directorCharacterId', as: 'directors' }); + + DirectorProposal.belongsTo(FalukantUser, { foreignKey: 'employerUserId', as: 'user' }); + FalukantUser.hasMany(DirectorProposal, { foreignKey: 'employerUserId', as: 'directorProposals' }); + + DirectorProposal.belongsTo(FalukantCharacter, { foreignKey: 'directorCharacterId', as: 'character' }); + FalukantCharacter.hasMany(DirectorProposal, { foreignKey: 'directorCharacterId', as: 'directorProposals' }); } diff --git a/backend/models/falukant/data/branch.js b/backend/models/falukant/data/branch.js index 3f1ccad..83aa2c2 100644 --- a/backend/models/falukant/data/branch.js +++ b/backend/models/falukant/data/branch.js @@ -18,7 +18,7 @@ Branch.init({ }, }, { sequelize, - modelName: 'BranchType', + modelName: 'Branch', tableName: 'branch', schema: 'falukant_data', timestamps: false, diff --git a/backend/models/falukant/data/buayble_stock.js b/backend/models/falukant/data/buyable_stock.js similarity index 100% rename from backend/models/falukant/data/buayble_stock.js rename to backend/models/falukant/data/buyable_stock.js diff --git a/backend/models/falukant/data/director.js b/backend/models/falukant/data/director.js new file mode 100644 index 0000000..558920d --- /dev/null +++ b/backend/models/falukant/data/director.js @@ -0,0 +1,48 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Director extends Model { } + +Director.init({ + directorCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + employerUserId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + income: { + type: DataTypes.INTEGER, + allowNull: false, + }, + satisfaction: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 100, + }, + mayProduce: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + maySell: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + mayStartTransport: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + } +}, { + sequelize, + modelName: 'Director', + tableName: 'director', + schema: 'falukant_data', + timestamps: false, + underscored: true, +}); + +export default Director; diff --git a/backend/models/falukant/data/director_proposal.js b/backend/models/falukant/data/director_proposal.js new file mode 100644 index 0000000..e58c0f7 --- /dev/null +++ b/backend/models/falukant/data/director_proposal.js @@ -0,0 +1,28 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class DirectorProposal extends Model { } + +DirectorProposal.init({ + directorCharacterId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + employerUserId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + proposedIncome: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'DirectorProposal', + tableName: 'director_proposal', + schema: 'falukant_data', + timestamps: true, + underscored: true, +}); + +export default DirectorProposal; diff --git a/backend/models/falukant/data/stock.js b/backend/models/falukant/data/stock.js index 831e1b7..cc3f3d8 100644 --- a/backend/models/falukant/data/stock.js +++ b/backend/models/falukant/data/stock.js @@ -19,7 +19,7 @@ FalukantStock.init({ }, }, { sequelize, - modelName: 'StockType', + modelName: 'StockData', tableName: 'stock', schema: 'falukant_data', timestamps: false, diff --git a/backend/models/falukant/log/moneyflow.js b/backend/models/falukant/log/moneyflow.js new file mode 100644 index 0000000..5e84886 --- /dev/null +++ b/backend/models/falukant/log/moneyflow.js @@ -0,0 +1,45 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class MoneyFlow extends Model { } + +MoneyFlow.init({ + falukantUserId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + activity: { + type: DataTypes.STRING, + allowNull: false, + }, + moneyBefore: { + type: DataTypes.DOUBLE, + allowNull: false, + }, + moneyAfter: { + type: DataTypes.DOUBLE, + allowNull: true, + }, + time: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW + }, + changeValue: { + type: DataTypes.DOUBLE, + allowNull: false, + }, + changedBy: { + type: DataTypes.INTEGER, + allowNull: true, + }, +}, { + sequelize, + modelName: 'MoneyFlow', + tableName: 'moneyflow', + schema: 'falukant_log', + timestamps: false, + underscored: true, +}); + +export default MoneyFlow; diff --git a/backend/models/index.js b/backend/models/index.js index 5a0fd91..a3ce448 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -48,7 +48,10 @@ import BranchType from './falukant/type/branch.js'; import Branch from './falukant/data/branch.js'; import Production from './falukant/data/production.js'; import Inventory from './falukant/data/inventory.js'; -import BuyableStock from './falukant/data/buayble_stock.js'; +import BuyableStock from './falukant/data/buyable_stock.js'; +import MoneyFlow from './falukant/log/moneyflow.js'; +import Director from './falukant/data/director.js'; +import DirectorProposal from './falukant/data/director_proposal.js'; const models = { SettingsType, @@ -102,6 +105,9 @@ const models = { Production, Inventory, BuyableStock, + MoneyFlow, + Director, + DirectorProposal, }; export default models; diff --git a/backend/models/trigger.js b/backend/models/trigger.js index 73b9da2..c298082 100644 --- a/backend/models/trigger.js +++ b/backend/models/trigger.js @@ -124,6 +124,62 @@ export async function createTriggers() { EXECUTE FUNCTION falukant_data.create_knowledge_trigger(); `; + const updateMoney = ` + CREATE OR REPLACE FUNCTION falukant_data.update_money( + p_falukant_user_id integer, + p_money_change numeric, + p_activity text, + p_changed_by integer DEFAULT NULL + ) + RETURNS void + LANGUAGE plpgsql + AS $function$ + DECLARE + v_money_before numeric(10,2); + v_money_after numeric(10,2); + v_moneyflow_id bigint; + BEGIN + SELECT money + INTO v_money_before + FROM falukant_data.falukant_user + WHERE id = p_falukant_user_id; + IF NOT FOUND THEN + RAISE EXCEPTION 'FalukantUser mit ID % nicht gefunden', p_falukant_user_id; + END IF; + v_money_after := v_money_before + p_money_change; + INSERT INTO falukant_log.moneyflow ( + falukant_user_id, + activity, + money_before, + money_after, + change_value, + changed_by, + time + ) + VALUES ( + p_falukant_user_id, + p_activity, + v_money_before, + NULL, -- Wird gleich aktualisiert + p_money_change, + p_changed_by, + NOW() + ) + RETURNING id INTO v_moneyflow_id; + UPDATE falukant_data.falukant_user + SET money = v_money_after + WHERE id = p_falukant_user_id; + UPDATE falukant_log.moneyflow + SET money_after = ( + SELECT money + FROM falukant_data.falukant_user + WHERE id = p_falukant_user_id + ) + WHERE id = v_moneyflow_id; + END; + $function$; + `; + try { await sequelize.query(createTriggerFunction); await sequelize.query(createInsertTrigger); @@ -136,6 +192,7 @@ export async function createTriggers() { await sequelize.query(createCharacterCreationTrigger); await sequelize.query(createKnowledgeTriggerMethod); await sequelize.query(createKnowledgeTrigger); + await sequelize.query(updateMoney); console.log('Triggers created successfully'); } catch (error) { diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 4561c22..d2b2cf0 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -11,11 +11,24 @@ router.get('/name/randomlastname', falukantController.randomLastName); router.get('/info', falukantController.getInfo); router.get('/branches/:branch', falukantController.getBranch); router.get('/branches', falukantController.getBranches); +router.get('/productions', falukantController.getAllProductions); router.post('/production', falukantController.createProduction); router.get('/production/:branchId', falukantController.getProduction); +router.get('/stocktypes', falukantController.getStockTypes); +router.get('/stockoverview', falukantController.getStockOverview); router.get('/stock/?:branchId', falukantController.getStock); router.post('/stock', falukantController.createStock); router.get('/products', falukantController.getProducts); router.get('/inventory/?:branchId', falukantController.getInventory); +router.post('/sell/all', falukantController.sellAllProducts); router.post('/sell', falukantController.sellProduct); +router.post('/moneyhistory', falukantController.moneyHistory); +router.get('/storage/:branchId', falukantController.getStorage); +router.post('/storage', falukantController.buyStorage); +router.delete('/storage', falukantController.sellStorage); +router.post('/director/proposal', falukantController.getDirectorProposals); +router.post('/director/convertproposal', falukantController.convertProposalToDirector); +router.post('/director/settings', falukantController.setSetting); +router.get('/director/:branchId', falukantController.getDirectorForBranch); + export default router; diff --git a/backend/services/authService.js b/backend/services/authService.js index 99ef82f..be329b8 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -33,7 +33,7 @@ const getFriends = async (userId) => { }, { model: User, - as: 'friendReceiver', + as: 'friendReceiver', attributes: ['hashedId', 'username'], }, ], @@ -104,12 +104,14 @@ export const loginUser = async ({ username, password }) => { user.authCode = authCode; await user.save(); const friends = await getFriends(user.id); + console.log('send login to friends'); for (const friend of friends) { await notifyUser(friend.hashedId, 'friendloginchanged', { userId: user.hashedId, status: 'online', }); } + console.log('set user session'); const sessionData = { id: user.hashedId, username: user.username, @@ -118,6 +120,7 @@ export const loginUser = async ({ username, password }) => { timestamp: Date.now() }; await setUserSession(user.id, sessionData); + console.log('get user params'); const params = await UserParam.findAll({ where: { userId: user.id @@ -133,11 +136,12 @@ export const loginUser = async ({ username, password }) => { const mappedParams = params.map(param => { return { 'name': param.paramType.description, 'value': param.value }; }); + console.log('return user'); return { id: user.hashedId, username: user.username, active: user.active, - param: mappedParams, + param: mappedParams, authCode }; }; diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index c9a42f1..4ac208f 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -1,129 +1,107 @@ import BaseService from './BaseService.js'; +import { Sequelize, Op } from 'sequelize'; + import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js'; import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js'; import FalukantUser from '../models/falukant/data/user.js'; -import User from '../models/community/user.js'; import FalukantCharacter from '../models/falukant/data/character.js'; import RegionData from '../models/falukant/data/region.js'; import RegionType from '../models/falukant/type/region.js'; -import { Sequelize } from 'sequelize'; import FalukantStock from '../models/falukant/data/stock.js'; import FalukantStockType from '../models/falukant/type/stock.js'; -import { notifyUser } from '../utils/socket.js'; -import { differenceInDays } from 'date-fns'; import TitleOfNobility from '../models/falukant/type/title_of_nobility.js'; import Branch from '../models/falukant/data/branch.js'; import BranchType from '../models/falukant/type/branch.js'; import Production from '../models/falukant/data/production.js'; import ProductType from '../models/falukant/type/product.js'; -import { Op, fn, col } from 'sequelize'; import Knowledge from '../models/falukant/data/product_knowledge.js'; import Inventory from '../models/falukant/data/inventory.js'; -import Stock from '../models/falukant/data/stock.js'; +import MoneyFlow from '../models/falukant/log/moneyflow.js'; +import User from '../models/community/user.js'; +import { notifyUser } from '../utils/socket.js'; +import { differenceInDays } from 'date-fns'; +import { updateFalukantUserMoney } from '../utils/sequelize.js'; +import BuyableStock from '../models/falukant/data/buyable_stock.js'; +import DirectorProposal from '../models/falukant/data/director_proposal.js'; +import Director from '../models/falukant/data/director.js'; + + +function calcAge(birthdate) { + const b = new Date(birthdate); b.setHours(0, 0); + const now = new Date(); now.setHours(0, 0); + return differenceInDays(now, b); +} + +async function getFalukantUserOrFail(hashedId) { + const user = await FalukantUser.findOne({ + include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }] + }); + if (!user) throw new Error('User not found'); + return user; +} + +async function getBranchOrFail(userId, branchId) { + const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: userId } }); + if (!branch) throw new Error('Branch not found'); + return branch; +} + +function calcSellPrice(product, knowledgeFactor = 0) { + const max = product.sellCost; + const min = max * 0.6; + return min + (max - min) * (knowledgeFactor / 100); +} class FalukantService extends BaseService { async getFalukantUserByHashedId(hashedId) { - const falukantUser = await FalukantUser.findOne({ - include: [{ - model: User, - as: 'user', - attributes: ['username', 'hashedId'], - where: { - hashedId: hashedId - }, - } - ], + return FalukantUser.findOne({ + include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }] }); - return falukantUser; } async getUser(hashedUserId) { - const falukantUser = await FalukantUser.findOne({ - include: [{ - model: User, - as: 'user', - attributes: ['username', 'hashedId'], - where: { - hashedId: hashedUserId + const u = await FalukantUser.findOne({ + include: [ + { model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId: hashedUserId } }, + { + model: FalukantCharacter, + as: 'character', + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }, + { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] } + ], + attributes: ['birthdate', 'gender'] }, - }, - { - model: FalukantCharacter, - as: 'character', - include: [ - { - model: FalukantPredefineFirstname, - as: 'definedFirstName', - attributes: ['name'] - }, - { - model: FalukantPredefineLastname, - as: 'definedLastName', - attributes: ['name'] - }, - { - model: TitleOfNobility, - as: 'nobleTitle', - attributes: ['labelTr'] - } - ], - attributes: ['birthdate', 'gender'], - }, - { - model: RegionData, - as: 'mainBranchRegion', - include: [ - { - model: RegionType, - as: 'regionType', - } - ], - attributes: ['name'], - }, - { - model: Branch, - as: 'branches', - include: [ - { - model: BranchType, - as: 'branchType', - attributes: ['labelTr'] - }, - { - model: RegionData, - as: 'region', - include: [ - { - model: RegionType, - as: 'regionType', - } - ], - attributes: ['name'], - } - ] - } + { + model: RegionData, + as: 'mainBranchRegion', + include: [{ model: RegionType, as: 'regionType' }], + attributes: ['name'] + }, + { + model: Branch, + as: 'branches', + include: [ + { model: BranchType, as: 'branchType', attributes: ['labelTr'] }, + { + model: RegionData, + as: 'region', + include: [{ model: RegionType, as: 'regionType' }], + attributes: ['name'] + } + ] + } ], - attributes: ['money', 'creditAmount', 'todayCreditTaken'], + attributes: ['money', 'creditAmount', 'todayCreditTaken'] }); - if (!falukantUser) { - throw new Error('User not found'); - } - const character = falukantUser.character; - if (character && character.birthdate) { - const birthdate = new Date(character.birthdate); - birthdate.setHours(0); - birthdate.setMinutes(0); - const currentDate = new Date(); - currentDate.setHours(0); - currentDate.setMinutes(0); - const ageInDays = differenceInDays(currentDate, birthdate); - character.setDataValue('age', ageInDays); - } - return falukantUser; + if (!u) throw new Error('User not found'); + if (u.character?.birthdate) u.character.setDataValue('age', calcAge(u.character.birthdate)); + return u; } async randomFirstName(gender) { - const names = await FalukantPredefineFirstname.findAll({ where: { gender: gender } }); + const names = await FalukantPredefineFirstname.findAll({ where: { gender } }); return names[Math.floor(Math.random() * names.length)].name; } @@ -133,444 +111,800 @@ class FalukantService extends BaseService { } async createUser(hashedUserId, gender, firstName, lastName) { - try { - const user = await this.getUserByHashedId(hashedUserId); - const userExistsCheck = await FalukantUser.findOne({ where: { userId: user.id } }); - if (userExistsCheck) { - throw new Error('User already exists in Falukant.'); - } - let firstNameObject = await FalukantPredefineFirstname.findOne({ where: { name: firstName } }); - let lastNameObject = await FalukantPredefineLastname.findOne({ where: { name: lastName } }); - if (!firstNameObject) { - firstNameObject = await FalukantPredefineFirstname.create({ name: firstName, gender: gender }); - } - if (!lastNameObject) { - lastNameObject = await FalukantPredefineLastname.create({ name: lastName }); - } - const randomRegion = await RegionData.findOne({ - order: Sequelize.fn('RANDOM'), - limit: 1, - include: [ - { - model: RegionType, - as: 'regionType', - where: { - labelTr: 'city' - } - } - ] - }); - if (!randomRegion) { - throw new Error('No region found with the label "city".'); - } - const titleOfNobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } }); - if (!titleOfNobility) { - throw new Error('No title of nobility found with the label "noncivil".'); - } - const falukantUser = await FalukantUser.create({ - userId: user.id, - money: 50.00, - creditAmount: 0.00, - todayCreditTaken: 0.00, - creditInterestRate: 0.00, - mainBranchRegionId: randomRegion.id, - }); - const fourteenDaysAgo = new Date(); - fourteenDaysAgo.setDate(fourteenDaysAgo.getDate() - 14); - const character = await FalukantCharacter.create({ - userId: falukantUser.id, - regionId: randomRegion.id, - firstName: firstNameObject.id, - lastName: lastNameObject.id, - gender: gender, - birthdate: fourteenDaysAgo, - titleOfNobility: titleOfNobility.id, - }); - await FalukantStock.create({ - userId: falukantUser.id, - regionId: randomRegion.id, - stockTypeId: (await FalukantStockType.findOne({ where: [{ label_tr: 'wood' }] })).id, - quantity: 10, - }); - falukantUser['character'] = character; - const branchType = await BranchType.findOne({ where: { labelTr: 'fullstack' } }); - await Branch.create({ - userId: falukantUser.id, - regionId: randomRegion.id, - branchTypeId: branchType.id, - }) - notifyUser(user.hashedId, 'reloadmenu', {}); - return falukantUser; - } catch (error) { - console.error('Error creating character'); - console.log(error); - } + const user = await this.getUserByHashedId(hashedUserId); + if (await FalukantUser.findOne({ where: { userId: user.id } })) throw new Error('User already exists in Falukant.'); + let fnObj = await FalukantPredefineFirstname.findOne({ where: { name: firstName } }); + let lnObj = await FalukantPredefineLastname.findOne({ where: { name: lastName } }); + if (!fnObj) fnObj = await FalukantPredefineFirstname.create({ name: firstName, gender }); + if (!lnObj) lnObj = await FalukantPredefineLastname.create({ name: lastName }); + const region = await RegionData.findOne({ + order: Sequelize.fn('RANDOM'), + limit: 1, + include: [{ model: RegionType, as: 'regionType', where: { labelTr: 'city' } }] + }); + if (!region) throw new Error('No region found with the label "city".'); + const nobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } }); + if (!nobility) throw new Error('No title of nobility found with the label "noncivil".'); + const falukantUser = await FalukantUser.create({ + userId: user.id, money: 50, creditAmount: 0, todayCreditTaken: 0, creditInterestRate: 0, mainBranchRegionId: region.id + }); + const date = new Date(); date.setDate(date.getDate() - 14); + const ch = await FalukantCharacter.create({ + userId: falukantUser.id, regionId: region.id, firstName: fnObj.id, lastName: lnObj.id, gender, birthdate: date, titleOfNobility: nobility.id + }); + const stType = await FalukantStockType.findOne({ where: [{ label_tr: 'wood' }] }); + await FalukantStock.create({ userId: falukantUser.id, regionId: region.id, stockTypeId: stType.id, quantity: 10 }); + falukantUser.character = ch; + const bType = await BranchType.findOne({ where: { labelTr: 'fullstack' } }); + await Branch.create({ userId: falukantUser.id, regionId: region.id, branchTypeId: bType.id }); + notifyUser(user.hashedId, 'reloadmenu', {}); + return falukantUser; } async getInfo(hashedUserId) { - try { - const user = await User.findOne({ where: { hashedId: hashedUserId } }); - if (!user) { - throw new Error('User not found'); - } - const falukantUser = await FalukantUser.findOne({ - include: [{ - model: User, - as: 'user', - attributes: ['hashedId'], - where: { - hashedId: hashedUserId - }, - }, - { - model: FalukantCharacter, - as: 'character', - attributes: ['birthdate', 'health'], - }, - ], - attributes: ['money'] - }); - if (!falukantUser) { - throw new Error('User not found'); - } - const character = falukantUser.character; - if (character && character.birthdate) { - const birthdate = new Date(character.birthdate); - birthdate.setHours(0); - birthdate.setMinutes(0); - const currentDate = new Date(); - currentDate.setHours(0); - currentDate.setMinutes(0); - const ageInDays = differenceInDays(currentDate, birthdate); - character.setDataValue('age', ageInDays); - } - return falukantUser; - } catch (error) { - console.error('Error getting character info'); - console.log(error); - } + const user = await User.findOne({ where: { hashedId: hashedUserId } }); + if (!user) throw new Error('User not found'); + const falukantUser = await FalukantUser.findOne({ + include: [ + { model: User, as: 'user', attributes: ['hashedId'], where: { hashedId: hashedUserId } }, + { model: FalukantCharacter, as: 'character', attributes: ['birthdate', 'health'] } + ], + attributes: ['money'] + }); + if (!falukantUser) throw new Error('User not found'); + if (falukantUser.character?.birthdate) falukantUser.character.setDataValue('age', calcAge(falukantUser.character.birthdate)); + return falukantUser; } async getBranches(hashedUserId) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const branches = await Branch.findAll({ - where: { falukantUserId: falukantUser.id }, - include: [ - { - model: BranchType, - as: 'branchType', - attributes: ['labelTr'], - }, - { - model: RegionData, - as: 'region', - attributes: ['name'], - } - ], - attributes: ['id', 'regionId'], - order: [['branchTypeId', 'ASC']], - }); - const enrichedBranches = branches.map(branch => ({ - ...branch.toJSON(), - isMainBranch: falukantUser.mainBranchRegionId === branch.regionId, - })); - return enrichedBranches; - } catch (error) { - console.error('Error in getBranches:', error); - throw new Error('Failed to retrieve branches'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const bs = await Branch.findAll({ + where: { falukantUserId: u.id }, + include: [ + { model: BranchType, as: 'branchType', attributes: ['labelTr'] }, + { model: RegionData, as: 'region', attributes: ['name'] } + ], + attributes: ['id', 'regionId'], + order: [['branchTypeId', 'ASC']] + }); + return bs.map(b => ({ ...b.toJSON(), isMainBranch: u.mainBranchRegionId === b.regionId })); } async getBranch(hashedUserId, branchId) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const branch = await Branch.findOne({ - where: { id: branchId, falukantUserId: falukantUser.id }, - include: [ - { - model: BranchType, - as: 'branchType', - attributes: ['labelTr'], - }, - { - model: RegionData, - as: 'region', - attributes: ['name'], - }, - { - model: Production, - as: 'productions', - attributes: ['quantity', 'startTimestamp'], - include: [ - { - model: ProductType, - as: 'productType', - attributes: ['id', 'category', 'labelTr', 'sellCost', 'productionTime'], - } - ] - } - ], - attributes: ['id', 'regionId'], - }); - if (!branch) { - throw new Error('Branch not found'); - } - return branch; - } catch (error) { - console.error('Error in getBranch:', error); - throw new Error('Failed to retrieve branch'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const br = await Branch.findOne({ + where: { id: branchId, falukantUserId: u.id }, + include: [ + { model: BranchType, as: 'branchType', attributes: ['labelTr'] }, + { model: RegionData, as: 'region', attributes: ['name'] }, + { + model: Production, + as: 'productions', + attributes: ['quantity', 'startTimestamp'], + include: [{ model: ProductType, as: 'productType', attributes: ['id', 'category', 'labelTr', 'sellCost', 'productionTime'] }] + } + ], + attributes: ['id', 'regionId'] + }); + if (!br) throw new Error('Branch not found'); + return br; } async getStock(hashedUserId, branchId) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: falukantUser.id } }); - if (!branch) { - throw new Error('Branch not found'); - } - const stock = await FalukantStock.findOne({ where: { regionId: branch.regionId, userId: falukantUser.id } }); - return stock; - } catch (error) { - console.error('Error in getStock:', error); - throw new Error('Failed to retrieve stock'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const b = await getBranchOrFail(u.id, branchId); + return FalukantStock.findAll({ where: { regionId: b.regionId, userId: u.id } }); } async createStock(hashedUserId, branchId, stockData) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: falukantUser.id } }); - if (!branch) { - throw new Error('Branch not found'); - } - const stock = await FalukantStock.create({ - userId: falukantUser.id, - regionId: branch.regionId, - stockTypeId: stockData.stockTypeId, - quantity: stockData.quantity, - }); - return stock; - } catch (error) { - console.error('Error in createStock:', error); - throw new Error('Failed to create stock'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const b = await getBranchOrFail(u.id, branchId); + return FalukantStock.create({ + userId: u.id, regionId: b.regionId, stockTypeId: stockData.stockTypeId, quantity: stockData.quantity + }); } async createProduction(hashedUserId, branchId, productId, quantity) { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - - const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: falukantUser.id } }); - if (!branch) { - throw new Error('Branch not found'); - } - const product = await ProductType.findOne({ where: { id: productId } }); - if (falukantUser.money < quantity * product.category * 7) { - throw new Error('notenoughmoney'); - } - const production = await Production.create({ - branchId: branch.id, - productId: productId, - quantity: quantity, - }); - falukantUser.update({ money: falukantUser.money - quantity * product.category * 7 }); - notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {}); - notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id }); - return production; + const u = await getFalukantUserOrFail(hashedUserId); + const b = await getBranchOrFail(u.id, branchId); + const p = await ProductType.findOne({ where: { id: productId } }); + if (!p) throw new Error('Product not found'); + const cost = quantity * p.category * 6; + if (u.money < cost) throw new Error('notenoughmoney'); + const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id); + if (!r.success) throw new Error('Failed to update money'); + const d = await Production.create({ branchId: b.id, productId, quantity }); + notifyUser(u.user.hashedId, 'falukantUpdateStatus', {}); + notifyUser(u.user.hashedId, 'falukantBranchUpdate', { branchId: b.id }); + return d; } async getProduction(hashedUserId, branchId) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: falukantUser.id } }); - if (!branch) { - throw new Error('Branch not found'); - } - const production = await FalukantProduction.findOne({ where: { regionId: branch.regionId } }); - return production; - } catch (error) { - console.error('Error in getProduction:', error); - throw new Error('Failed to retrieve production'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const b = await getBranchOrFail(u.id, branchId); + return Production.findOne({ where: { regionId: b.regionId } }); } async getProducts(hashedUserId) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const products = await ProductType.findAll({ - where: { - category: { - [Op.lte]: falukantUser.certificate - } - }, - include: [ - { - model: Knowledge, - as: 'knowledges', - attributes: ['knowledge'], - } - ], - attributes: ['labelTr', 'id', 'sellCost', 'productionTime', 'category'], - }); - return products; - } catch (error) { - console.error('Error in getProducts:', error); - throw new Error('Failed to retrieve products'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const c = await FalukantCharacter.findOne({ where: { userId: u.id } }); + const ps = await ProductType.findAll({ + where: { category: { [Op.lte]: u.certificate } }, + include: [{ model: Knowledge, as: 'knowledges', attributes: ['knowledge'], where: { characterId: c.id } }], + attributes: ['labelTr', 'id', 'sellCost', 'productionTime', 'category'] + }); + return ps; } async getInventory(hashedUserId, branchId) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); - } - const branchFilter = branchId - ? { id: branchId, falukantUserId: falukantUser.id } - : { falukantUserId: falukantUser.id }; - const branches = await Branch.findAll({ - where: branchFilter, - include: [ - { - model: FalukantStock, - as: 'stocks', - include: [ - { - model: FalukantStockType, - as: 'stockType', - }, - ], - }, - { - model: RegionData, - as: 'region', - include: [ - { - model: RegionType, - as: 'regionType', - }, - ], - }, - ], - }); - const stockIds = branches.flatMap(branch => branch.stocks.map(stock => stock.id)); - const inventoryItems = await Inventory.findAll({ - where: { stockId: stockIds }, - include: [ - { - model: FalukantStock, - as: 'stock', - include: [ - { - model: Branch, - as: 'branch', - include: [ - { - model: RegionData, - as: 'region', - include: [ - { - model: RegionType, - as: 'regionType', - }, - ], - }, - ], - }, - { - model: FalukantStockType, - as: 'stockType', - }, - ], - }, - { - model: ProductType, - as: 'productType', - }, - ], - }); - const groupedInventory = inventoryItems.reduce((acc, item) => { - const region = item.stock.branch.region; - const key = `${region.id}-${item.productType.id}-${item.quality}`; - if (!acc[key]) { - acc[key] = { - region, - product: item.productType, - quality: item.quality, - totalQuantity: 0, - }; - } - acc[key].totalQuantity += item.quantity; - return acc; - }, {}); - const sortedInventory = Object.values(groupedInventory).sort((a, b) => { - if (a.region.id !== b.region.id) { - return a.region.id - b.region.id; - } - if (a.product.id !== b.product.id) { - return a.product.id - b.product.id; - } - return a.quality - b.quality; - }); - - return sortedInventory; - } catch (error) { - console.error('Error in getInventory:', error); - throw new Error('Failed to retrieve inventory'); - } + const u = await getFalukantUserOrFail(hashedUserId); + const f = branchId ? { id: branchId, falukantUserId: u.id } : { falukantUserId: u.id }; + const br = await Branch.findAll({ + where: f, + include: [ + { model: FalukantStock, as: 'stocks', include: [{ model: FalukantStockType, as: 'stockType' }] }, + { model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] } + ] + }); + const stockIds = br.flatMap(b => b.stocks.map(s => s.id)); + const inv = await Inventory.findAll({ + where: { stockId: stockIds }, + include: [ + { + model: FalukantStock, + as: 'stock', + include: [ + { + model: Branch, + as: 'branch', + include: [{ model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }] + }, + { model: FalukantStockType, as: 'stockType' } + ] + }, + { model: ProductType, as: 'productType' } + ] + }); + const grouped = inv.reduce((acc, i) => { + const r = i.stock.branch.region; + const k = `${r.id}-${i.productType.id}-${i.quality}`; + acc[k] = acc[k] || { region: r, product: i.productType, quality: i.quality, totalQuantity: 0 }; + acc[k].totalQuantity += i.quantity; + return acc; + }, {}); + return Object.values(grouped).sort((a, b) => { + if (a.region.id !== b.region.id) return a.region.id - b.region.id; + if (a.product.id !== b.product.id) return a.product.id - b.product.id; + return a.quality - b.quality; + }); } async sellProduct(hashedUserId, branchId, productId, quality, quantity) { - try { - const falukantUser = await this.getFalukantUserByHashedId(hashedUserId); - if (!falukantUser) { - throw new Error('User not found'); + const user = await getFalukantUserOrFail(hashedUserId); + const branch = await getBranchOrFail(user.id, branchId); + const character = await FalukantCharacter.findOne({ where: { userId: user.id } }); + if (!character) throw new Error('No character found for user'); + + const stock = await FalukantStock.findOne({ where: { branchId: branch.id } }); + if (!stock) throw new Error('Stock not found'); + + const inventory = await Inventory.findAll({ + where: { stockId: stock.id, quality }, + include: [ + { + model: ProductType, + as: 'productType', + required: true, + where: { id: productId }, + include: [ + { + model: Knowledge, + as: 'knowledges', + required: false, + where: { characterId: character.id } + } + ] + } + ] + }); + + if (!inventory.length) throw new Error('No inventory found'); + const available = inventory.reduce((sum, i) => sum + i.quantity, 0); + if (available < quantity) throw new Error('Not enough inventory available'); + + const item = inventory[0].productType; + const knowledgeVal = item.knowledges?.[0]?.knowledge || 0; + const revenue = quantity * calcSellPrice(item, knowledgeVal); + + const moneyResult = await updateFalukantUserMoney(user.id, revenue, 'Product sale', user.id); + if (!moneyResult.success) throw new Error('Failed to update money'); + + let remaining = quantity; + for (const inv of inventory) { + if (inv.quantity <= remaining) { + remaining -= inv.quantity; + await inv.destroy(); + } else { + await inv.update({ quantity: inv.quantity - remaining }); + remaining = 0; + break; } - const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: falukantUser.id } }); - if (!branch) { - throw new Error('Branch not found'); + } + + notifyUser(user.user.hashedId, 'falukantUpdateStatus', {}); + notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id }); + return { success: true }; + } + + async sellAllProducts(hashedUserId, branchId) { + const falukantUser = await getFalukantUserOrFail(hashedUserId); + const branch = await Branch.findOne({ + where: { id: branchId, falukantUserId: falukantUser.id }, + include: [{ model: FalukantStock, as: 'stocks' }] + }); + if (!branch) throw new Error('Branch not found'); + const stockIds = branch.stocks.map(s => s.id); + const character = await FalukantCharacter.findOne({ where: { userId: falukantUser.id } }); + if (!character) throw new Error('No character for user'); + const inventory = await Inventory.findAll({ + where: { stockId: stockIds }, + include: [ + { + model: ProductType, + as: 'productType', + include: [ + { + model: Knowledge, + as: 'knowledges', + required: false, + where: { + characterId: character.id + } + } + ] + }, + { + model: FalukantStock, + as: 'stock', + include: [ + { + model: Branch, + as: 'branch' + }, + { + model: FalukantStockType, + as: 'stockType' + } + ] + } + ] + }); + if (!inventory.length) return { success: true, revenue: 0 }; + let total = 0; + for (const item of inventory) { + const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0; + total += item.quantity * calcSellPrice(item.productType, knowledgeVal); + } + const moneyResult = await updateFalukantUserMoney( + falukantUser.id, + total, + 'Sell all products', + falukantUser.id + ); + if (!moneyResult.success) throw new Error('Failed to update money'); + for (const item of inventory) { + await Inventory.destroy({ where: { id: item.id } }); + } + notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {}); + notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id }); + return { success: true, revenue: total }; + } + + async moneyHistory(hashedUserId, page = 1, filter = '') { + const u = await getFalukantUserOrFail(hashedUserId); + const limit = 25, offset = (page - 1) * limit; + const w = { falukantUserId: u.id }; + if (filter) w.activity = { [Op.iLike]: `%${filter}%` }; + const { rows, count } = await MoneyFlow.findAndCountAll({ + where: w, order: [['time', 'DESC']], limit, offset + }); + return { data: rows, total: count, currentPage: page, totalPages: Math.ceil(count / limit) }; + } + + async getStorage(hashedUserId, branchId) { + const user = await getFalukantUserOrFail(hashedUserId); + const branch = await getBranchOrFail(user.id, branchId); + const stocks = await FalukantStock.findAll({ + where: { branchId: branch.id }, + include: [{ model: FalukantStockType, as: 'stockType' }], + }); + const stockIds = stocks.map(s => s.id); + const inventoryItems = await Inventory.findAll({ + where: { stockId: stockIds }, + include: [ + { + model: FalukantStock, + as: 'stock', + include: [{ model: FalukantStockType, as: 'stockType' }], + }, + ], + }); + let totalUsedCapacity = 0; + const usageByType = {}; + for (const s of stocks) { + const stId = s.stockTypeId; + if (!usageByType[stId]) { + usageByType[stId] = { + stockTypeId: stId, + stockTypeLabelTr: s.stockType?.labelTr || `stockType:${stId}`, + totalCapacity: 0, + used: 0 + }; } - const product = await ProductType.findOne({ where: { id: productId } }); - if (!product) { - throw new Error('Product not found'); + usageByType[stId].totalCapacity += s.quantity; + } + for (const item of inventoryItems) { + totalUsedCapacity += item.quantity; + const stId = item.stock.stockTypeId; + if (!usageByType[stId]) { + usageByType[stId] = { + stockTypeId: stId, + stockTypeLabelTr: item.stock.stockType?.labelTr || `stockType:${stId}`, + totalCapacity: 0, + used: 0 + }; } - const stock = await Stock.findOne({ where: { branchId: branch.id, } }); - if (!stock) { - throw new Error('Stock not found'); + usageByType[stId].used += item.quantity; + } + const buyableStocks = await BuyableStock.findAll({ + where: { regionId: branch.regionId }, + include: [{ model: FalukantStockType, as: 'stockType' }], + }); + const buyableByType = {}; + for (const b of buyableStocks) { + const stId = b.stockTypeId; + if (!buyableByType[stId]) { + buyableByType[stId] = { + stockTypeId: stId, + stockTypeLabelTr: b.stockType?.labelTr || `stockType:${stId}`, + quantity: 0 + }; } - if (stock.quantity < quantity) { - throw new Error('Not enough stock'); - } - await FalukantStock.decrement('quantity', { by: quantity, where: { regionId: branch.regionId, stockTypeId: product.id } }); - const inventory = await Inventory.create({ stockId: stock.id, quality, quantity }); - return inventory; - } catch (error) { - console.error('Error in sellProduct:', error); - throw new Error('Failed to sell product'); + buyableByType[stId].quantity += b.quantity; + } + let maxCapacity = stocks.reduce((sum, s) => sum + s.quantity, 0); + return { + branchId, + totalUsedCapacity, + maxCapacity, + usageByType: Object.values(usageByType), + buyableByType: Object.values(buyableByType) + }; + } + + async buyStorage(hashedUserId, branchId, amount, stockTypeId) { + const user = await getFalukantUserOrFail(hashedUserId); + const branch = await getBranchOrFail(user.id, branchId); + const buyable = await BuyableStock.findOne({ + where: { regionId: branch.regionId, stockTypeId }, + include: [{ model: FalukantStockType, as: 'stockType' }] + }); + if (!buyable || buyable.quantity < amount) throw new Error('Not enough buyable stock'); + const costPerUnit = buyable.stockType.cost; + const totalCost = costPerUnit * amount; + if (user.money < totalCost) throw new Error('notenoughmoney'); + const moneyResult = await updateFalukantUserMoney( + user.id, + -totalCost, + `Buy storage (type: ${buyable.stockType.labelTr})`, + user.id + ); + if (!moneyResult.success) throw new Error('Failed to update money'); + buyable.quantity -= amount; + await buyable.save(); + const stock = await FalukantStock.findOne({ + where: { branchId: branch.id, stockTypeId }, + include: [{ model: FalukantStockType, as: 'stockType' }] + }); + if (!stock) throw new Error('No stock record found for this branch and stockType'); + stock.quantity += amount; + await stock.save(); + return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr }; + } + + async sellStorage(hashedUserId, branchId, amount, stockTypeId) { + const user = await getFalukantUserOrFail(hashedUserId); + const branch = await getBranchOrFail(user.id, branchId); + const stock = await FalukantStock.findOne({ + where: { branchId: branch.id, stockTypeId }, + include: [{ model: FalukantStockType, as: 'stockType' }] + }); + if (!stock || stock.quantity < amount) throw new Error('Not enough stock to sell'); + const costPerUnit = stock.stockType.cost; + const totalRevenue = costPerUnit * amount; + const moneyResult = await updateFalukantUserMoney( + user.id, + totalRevenue, + `Sell storage (type: ${stock.stockType.labelTr})`, + user.id + ); + if (!moneyResult.success) throw new Error('Failed to update money'); + stock.quantity -= amount; + await stock.save(); + const buyable = await BuyableStock.findOne({ + where: { regionId: branch.regionId, stockTypeId }, + include: [{ model: FalukantStockType, as: 'stockType' }] + }); + if (!buyable) throw new Error('No buyable record found for this region and stockType'); + buyable.quantity += amount; + await buyable.save(); + return { success: true, sold: amount, totalRevenue, stockType: stock.stockType.labelTr }; + } + + async notifyRegionUsersAboutStockChange(regionId) { + const users = await FalukantCharacter.findAll({ + where: { + regionId: regionId + }, + include: [ + { + model: FalukantUser, + as: 'user', + include: [ + { + model: User, + as: 'user' + }, + { + model: Branch, + as: 'branch', + where: { + regionId: regionId + } + } + ] + } + ] + }); + for (const user of users) { + notifyUser(user.user[0].user[0].hashedId, 'stock_change', { branchId: user.user[0].branch[0].id }); } } -} + + async getStockTypes() { + return FalukantStockType.findAll(); + } + + async getStockOverview() { + const items = await Inventory.findAll({ + include: [ + { + model: FalukantStock, + as: 'stock', + include: [ + { + model: Branch, + as: 'branch', + include: [{ model: RegionData, as: 'region' }] + } + ] + }, + { + model: ProductType, + as: 'productType' + } + ] + }); + const result = items.map(inv => ({ + regionName: inv.stock?.branch?.region?.name || '???', + productLabelTr: inv.productType?.labelTr || '???', + quantity: inv.quantity + })); + return result; + } + + async getAllProductions(hashedUserId) { + const user = await getFalukantUserOrFail(hashedUserId); + const productions = await Production.findAll({ + include: [ + { + model: Branch, + as: 'branch', + include: [ + { model: RegionData, as: 'region', attributes: ['name'] } + ], + where: { falukantUserId: user.id } + }, + { model: ProductType, as: 'productType', attributes: ['labelTr', 'productionTime'] } + ], + attributes: ['startTimestamp', 'quantity'], + }); + const formattedProductions = productions.map((production) => { + const startTimestamp = new Date(production.startTimestamp).getTime(); + const endTimestamp = startTimestamp + production.productType.productionTime * 60 * 1000; + return { + cityName: production.branch.region.name, + productName: production.productType.labelTr, + quantity: production.quantity, + endTimestamp: new Date(endTimestamp).toISOString(), + }; + }); + formattedProductions.sort((a, b) => new Date(a.endTimestamp) - new Date(b.endTimestamp)); + return formattedProductions; + } + + async getDirectorProposals(hashedUserId, branchId) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + if (!user) { + throw new Error('User not found'); + } + const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id } }); + if (!branch) { + throw new Error('Branch not found or does not belong to the user'); + } + const { falukantUserId, regionId } = branch; + await this.deleteExpiredProposals(); + const existingProposals = await this.fetchProposals(falukantUserId, regionId); + if (existingProposals.length > 0) { + return this.formatProposals(existingProposals); + } + await this.generateProposals(falukantUserId, regionId); + const newProposals = await this.fetchProposals(falukantUserId, regionId); + return this.formatProposals(newProposals); + } + + async deleteExpiredProposals() { + const expirationTime = new Date(Date.now() - 24 * 60 * 60 * 1000); + await DirectorProposal.destroy({ + where: { + createdAt: { + [Op.lt]: expirationTime, + }, + }, + }); + } + + async fetchProposals(falukantUserId, regionId) { + return DirectorProposal.findAll({ + where: { employerUserId: falukantUserId }, + include: [ + { + model: FalukantCharacter, + as: 'character', + attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'], + where: { regionId }, + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName' }, + { model: FalukantPredefineLastname, as: 'definedLastName' }, + { model: TitleOfNobility, as: 'nobleTitle' }, + { + model: Knowledge, + as: 'knowledges', + include: [ + { model: ProductType, as: 'productType' }, + ] + }, + ], + }, + ], + }); + } + + async generateProposals(falukantUserId, regionId) { + const proposalCount = Math.floor(Math.random() * 3) + 3; + for (let i = 0; i < proposalCount; i++) { + const directorCharacter = await FalukantCharacter.findOne({ + where: { + regionId, + createdAt: { + [Op.lt]: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000), + }, + }, + order: Sequelize.fn('RANDOM'), + }); + if (!directorCharacter) { + throw new Error('No directors available for the region'); + } + const avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id); + const proposedIncome = Math.round( + directorCharacter.titleOfNobility * Math.pow(1.231, avgKnowledge / 1.5) + ); + await DirectorProposal.create({ + directorCharacterId: directorCharacter.id, + employerUserId: falukantUserId, + proposedIncome, + }); + } + } + + async calculateAverageKnowledge(characterId) { + const averageKnowledge = await Knowledge.findAll({ + where: { characterId }, + attributes: [[Sequelize.fn('AVG', Sequelize.col('knowledge')), 'avgKnowledge']], + raw: true, + }); + return parseFloat(averageKnowledge[0]?.avgKnowledge || 0); + } + + formatProposals(proposals) { + return proposals.map((proposal) => { + const age = Math.floor((Date.now() - new Date(proposal.character.birthdate)) / (24 * 60 * 60 * 1000)); + const knowledge = proposal.character.knowledges?.map(k => ({ + productId: k.productId, + value: k.knowledge, + labelTr: k.productType.labelTr, + })) || []; + return { + id: proposal.id, + proposedIncome: proposal.proposedIncome, + character: { + name: `${proposal.character.definedFirstName.name} ${proposal.character.definedLastName.name}`, + title: proposal.character.nobleTitle.labelTr, + age, + knowledge, + gender: proposal.character.gender, + }, + }; + }); + } + + async convertProposalToDirector(hashedUserId, proposalId) { + const user = await getFalukantUserOrFail(hashedUserId); + if (!user) { + throw new Error('User not found'); + } + const proposal = await DirectorProposal.findOne( + { + where: { id: proposalId }, + include: [ + { model: FalukantCharacter, as: 'character' }, + ] + } + ); + if (!proposal || proposal.employerUserId !== user.id) { + throw new Error('Proposal does not belong to the user'); + } + + const existingDirector = await Director.findOne({ + where: { + employerUserId: user.id + }, + include: [ + { + model: FalukantCharacter, + as: 'character', + where: { + regionId: proposal.character.regionId, + } + }, + ] + }); + if (existingDirector) { + throw new Error('A director already exists for this region'); + } + const newDirector = await Director.create({ + directorCharacterId: proposal.directorCharacterId, + employerUserId: proposal.employerUserId, + income: proposal.proposedIncome, + }); + const regionUserDirectorProposals = await DirectorProposal.findAll({ + where: { + employerUserId: proposal.employerUserId, + }, + include: [ + { + model: FalukantCharacter, + as: 'character', + where: { + regionId: proposal.character.regionId, + } + }, + ] + }); + if (regionUserDirectorProposals.length > 0) { + for (const proposal of regionUserDirectorProposals) { + await DirectorProposal.destroy(); + } + } + notifyUser(hashedUserId, 'directorchanged'); + return newDirector; + } + + async getDirectorForBranch(hashedUserId, branchId) { + const user = await getFalukantUserOrFail(hashedUserId); + if (!user) { + throw new Error('User not found'); + } + const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id } }); + if (!branch) { + throw new Error('Branch not found or does not belong to the user'); + } + const director = await Director.findOne({ + where: { employerUserId: user.id }, + include: [ + { + model: FalukantCharacter, + as: 'character', + attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'], + where: { + regionId: branch.regionId, + }, + include: [ + { + model: TitleOfNobility, + as: 'nobleTitle' + }, + { + model: FalukantPredefineFirstname, + as: 'definedFirstName' + }, + { + model: FalukantPredefineLastname, + as: 'definedLastName' + }, + ] + }, + ], + }); + if (!director) { + return null; + } + const age = Math.floor((Date.now() - new Date(director.character.birthdate)) / (24 * 60 * 60 * 1000)); + return { + director: { + id: director.id, + character: { + name: `${director.character.definedFirstName.name} ${director.character.definedLastName.name}`, + title: director.character.nobleTitle.labelTr, + age, + gender: director.character.gender, + }, + income: director.income, + satisfaction: director.satisfaction, + mayProduce: director.mayProduce, + maySell: director.maySell, + mayStartTransport: director.mayStartTransport, + }, + }; + } + + async setSetting(hashedUserId, branchId, directorId, settingKey, value) { + const user = await this.getFalukantUserByHashedId(hashedUserId); + const branch = await Branch.findOne({ + where: { + id: branchId, + falukantUserId: user.id, + }, + }); + if (!branch) { + return null; + } + const director = await Director.findOne({ + where: { + id: directorId, + employerUserId: user.id, + }, + include: [{ + model: FalukantCharacter, + as: 'character', + where: { + regionId: branch.regionId, + } + }] + }); + if (!director) { + return null; + } + const updateData = {}; + updateData[settingKey] = value || false; + + await Director.update(updateData, { + where: { + id: director.id, + }, + }); + return { result: 'ok' }; + } +} export default new FalukantService(); diff --git a/backend/utils/falukant/initializeFalukantPredefines.js b/backend/utils/falukant/initializeFalukantPredefines.js index fbc64dd..9e92cd8 100644 --- a/backend/utils/falukant/initializeFalukantPredefines.js +++ b/backend/utils/falukant/initializeFalukantPredefines.js @@ -214,12 +214,15 @@ const initializeFalukantLastnames = async () => { } async function initializeFalukantStockTypes() { - await FalukantStockType.bulkCreate([ - { labelTr: 'wood', cost: 15 }, - { labelTr: 'stone', cost: 25 }, - { labelTr: 'iron', cost: 100 }, - { labelTr: 'field', cost: 5 }, - ]); + try { + await FalukantStockType.bulkCreate([ + { labelTr: 'wood', cost: 15 }, + { labelTr: 'stone', cost: 25 }, + { labelTr: 'iron', cost: 100 }, + { labelTr: 'field', cost: 5 }, + ]); + } catch (error) { + } } async function initializeFalukantProducts() { @@ -338,4 +341,4 @@ async function initializeFalukantBranchTypes() { { labelTr: 'store', baseCost: 2000 }, { labelTr: 'fullstack', baseCost: 4500}, ], { ignoreDuplicates: true }); -} \ No newline at end of file +} diff --git a/backend/utils/redis.js b/backend/utils/redis.js index c46f862..28ea97d 100644 --- a/backend/utils/redis.js +++ b/backend/utils/redis.js @@ -9,7 +9,7 @@ const EXPIRATION_TIME = 30 * 60 * 1000; const redisClient = createClient({ url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, password: process.env.REDIS_PASSWORD, - legacyMode: false, + legacyMode: false, }); redisClient.connect().catch(console.error); @@ -23,7 +23,7 @@ const setUserSession = async (userId, sessionData) => { } catch (error) { console.error('Fehler beim Setzen der Benutzersitzung:', error); } -}; +}; const deleteUserSession = async (userId) => { try { @@ -41,8 +41,8 @@ const deleteUserSession = async (userId) => { const convertToOriginalType = (value) => { if (value === 'true') return true; if (value === 'false') return false; - if (!isNaN(value) && value.trim() !== '') return Number(value); - return value; + if (!isNaN(value) && value.trim() !== '') return Number(value); + return value; }; const getUserSession = async (userId) => { diff --git a/backend/utils/sequelize.js b/backend/utils/sequelize.js index 5b66074..f5b1115 100644 --- a/backend/utils/sequelize.js +++ b/backend/utils/sequelize.js @@ -20,6 +20,7 @@ const createSchemas = async () => { await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_data'); await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_type'); await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_predefine'); + await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_log'); }; const initializeDatabase = async () => { @@ -34,4 +35,37 @@ const syncModels = async (models) => { } }; -export { sequelize, initializeDatabase, syncModels }; +async function updateFalukantUserMoney(falukantUserId, moneyChange, activity, changedBy = null) { + try { + const result = await sequelize.query( + `SELECT falukant_data.update_money( + :falukantUserId, + :moneyChange, + :activity, + :changedBy + )`, + { + replacements: { + falukantUserId, + moneyChange, + activity, + changedBy, + }, + type: sequelize.QueryTypes.SELECT, + } + ); + return { + success: true, + message: 'Money updated successfully', + result + }; + } catch (error) { + console.error('Error updating money:', error); + return { + success: false, + message: error.message + }; + } +} + +export { sequelize, initializeDatabase, syncModels, updateFalukantUserMoney }; diff --git a/backend/utils/socket.js b/backend/utils/socket.js index 996a85b..1d2101a 100644 --- a/backend/utils/socket.js +++ b/backend/utils/socket.js @@ -4,12 +4,12 @@ import BaseService from '../services/BaseService.js'; const baseService = new BaseService(); let io; -const userSockets = {}; +const userSockets = {}; export function setupWebSocket(server) { io = new Server(server, { cors: { - origin: '*', + origin: '*', }, }); @@ -48,7 +48,9 @@ export async function notifyUser(recipientHashedUserId, event, data) { if (recipientUser) { const socketId = userSockets[recipientUser.hashedId]; if (socketId) { - io.to(socketId).emit(event, data); + setTimeout(() => { + io.to(socketId).emit(event, data); + }, 250); } } else { console.log(`Benutzer mit gehashter ID ${recipientHashedUserId} nicht gefunden.`); @@ -56,6 +58,7 @@ export async function notifyUser(recipientHashedUserId, event, data) { } catch (err) { console.error('Fehler beim Senden der Benachrichtigung:', err); } + console.log('done sending socket'); } export async function notifyAllUsers(event, data) { diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000..b1e94d9 Binary files /dev/null and b/dump.rdb differ diff --git a/frontend/dump.rdb b/frontend/dump.rdb new file mode 100644 index 0000000..8d3e059 Binary files /dev/null and b/frontend/dump.rdb differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 1b2a850..174cbdd 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -75,5 +75,6 @@ export default { display: flex; flex-direction: column; height: 100%; + overflow: hidden; } diff --git a/frontend/src/assets/styles.scss b/frontend/src/assets/styles.scss index 56a7d4b..3647f96 100644 --- a/frontend/src/assets/styles.scss +++ b/frontend/src/assets/styles.scss @@ -112,4 +112,16 @@ span.button:hover { .font-color-gender-nonbinary { color: #DAA520; +} + +main, +.contenthidden { + width: 100%; + height: 100%; + overflow: hidden; +} +.contentscroll { + width: 100%; + height: 100%; + overflow: auto; } \ No newline at end of file diff --git a/frontend/src/components/falukant/StatusBar.vue b/frontend/src/components/falukant/StatusBar.vue index a592a3f..0b91699 100644 --- a/frontend/src/components/falukant/StatusBar.vue +++ b/frontend/src/components/falukant/StatusBar.vue @@ -81,6 +81,7 @@ export default { width: calc(100% + 40px); gap: 2em; margin: -21px -20px 1.5em -20px; + position: fixed; } .status-item { diff --git a/frontend/src/dialogues/falukant/NewDirectorDialog.vue b/frontend/src/dialogues/falukant/NewDirectorDialog.vue new file mode 100644 index 0000000..924d0b1 --- /dev/null +++ b/frontend/src/dialogues/falukant/NewDirectorDialog.vue @@ -0,0 +1,162 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index 74144f4..8d553f1 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -50,7 +50,7 @@ "titles": { "male": { "noncivil": "Leibeigener", - "civil": "Bürgerlich", + "civil": "Freier Bürger", "sir": "Herr", "townlord": "Stadtherr", "by": "von", @@ -71,7 +71,7 @@ }, "female": { "noncivil": "Leibeigene", - "civil": "Bürgerlich", + "civil": "Freie Bürgerin", "sir": "Frau", "townlord": "Stadtherrin", "by": "zu", @@ -106,7 +106,18 @@ }, "director": { "title": "Direktor-Infos", - "info": "Informationen über den Direktor der Niederlassung." + "info": "Informationen über den Direktor der Niederlassung.", + "actions": { + "new": "Direktor einstellen" + }, + "name": "Name", + "salary": "Gehalt", + "satisfaction": "Zufriedenheit", + "fire": "Feuern", + "teach": "Weiterbilden", + "produce": "Darf produzieren", + "sell": "Darf verkaufen", + "starttransport": "Darf Transporte veranlassen" }, "sale": { "title": "Inventar", @@ -137,7 +148,8 @@ "time": "Uhr", "current": "Laufende Produktionen", "product": "Produkt", - "remainingTime": "Verbleibende Zeit (Sekunden)" + "remainingTime": "Verbleibende Zeit (Sekunden)", + "noProductions": "Keine laufenden Produktionen." }, "columns": { "city": "Stadt", @@ -155,7 +167,31 @@ "perMinute": "Erlös pro Minute", "expand": "Erträge anzeigen", "collapse": "Erträge ausblenden", - "knowledge": "Produktwissen" + "knowledge": "Produktwissen", + "profitAbsolute": "Gesamtgewinn", + "profitPerMinute": "Gewinn pro Minute" + }, + "storage": { + "title": "Lager", + "currentCapacity": "Verwendetes Lager", + "stockType": "Lagerart", + "totalCapacity": "Vorhanden", + "used": "Verwendet", + "availableToBuy": "Zum Kauf verfügbar", + "buyAmount": "Größe", + "buyStorageButton": "Kaufen", + "sellAmount": "Größe", + "sellStorageButton": "Verkaufen", + "selectStockType": "Lagertyp auswählen", + "costPerUnit": "Kosten pro Einheit", + "buycost": "Kosten", + "sellincome": "Einnahmen" + }, + "stocktype": { + "wood": "Holzlager", + "stone": "Steinlager", + "iron": "Eisenlager", + "field": "Feldlager" } }, "product": { @@ -194,6 +230,38 @@ }, "regionType": { "city": "Stadt" + }, + "moneyHistory": { + "title": "Geldhistorie", + "filter": "Filter", + "search": "Filter setzen", + "activity": "Aktivität", + "moneyBefore": "Geld vor der Transaktion", + "moneyAfter": "Geld nach der Transaktion", + "changeValue": "Wertänderung", + "time": "Zeit", + "activities": { + "Product sale": "Produktverkauf", + "Production cost": "Produktionskosten", + "Sell all products": "Alle Produkte verkaufen" + } + }, + "newdirector": { + "title": "Neuer Direktor", + "age": "Alter", + "salary": "Gehalt", + "skills": "Wissen", + "product": "Produkt", + "knowledge": "Produktwissen" + }, + "skillKnowledges": { + "excelent": "Exzellent", + "veryhigh": "Sehr gut", + "high": "Gut", + "medium": "Mittel", + "low": "Schlecht", + "verylow": "Sehr schlecht", + "none": "Kein Wissen" } } } \ No newline at end of file diff --git a/frontend/src/router/falukantRoutes.js b/frontend/src/router/falukantRoutes.js index 225146c..f620398 100644 --- a/frontend/src/router/falukantRoutes.js +++ b/frontend/src/router/falukantRoutes.js @@ -1,6 +1,7 @@ import BranchView from '../views/falukant/BranchView.vue'; import Createview from '../views/falukant/CreateView.vue'; import FalukantOverviewView from '../views/falukant/OverviewView.vue'; +import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue'; const falukantRoutes = [ { @@ -21,6 +22,12 @@ const falukantRoutes = [ component: BranchView, meta: { requiresAuth: true }, }, + { + path: '/falukant/moneyhistory', + name: 'MoneyHistoryView', + component: MoneyHistoryView, + meta: { requiresAuth: true }, + }, ]; export default falukantRoutes; diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 43b7301..cd94234 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -56,7 +56,7 @@ const store = createStore({ }, clearDaemonSocket(state) { if (state.daemonSocket) { - state.daemonSocket.disconnect(); + state.daemonSocket.close(); } state.daemonSocket = null; }, @@ -73,6 +73,7 @@ const store = createStore({ await dispatch('loadMenu'); }, logout({ commit }) { + console.log('Logging out...'); commit('clearSocket'); commit('clearDaemonSocket'); commit('dologout'); @@ -80,35 +81,45 @@ const store = createStore({ }, initializeSocket({ commit, state }) { if (state.isLoggedIn && state.user) { + let currentSocket = state.socket; const connectSocket = () => { + if (currentSocket) { + currentSocket.disconnect(); + } const socket = io(import.meta.env.VITE_API_BASE_URL); - + socket.on('connect', () => { console.log('Socket.io connected'); - socket.emit('setUserId', state.user.id); + socket.emit('setUserId', state.user.id); // Sende user.id, wenn user vorhanden ist }); - + socket.on('disconnect', (reason) => { console.warn('Socket.io disconnected:', reason); retryConnection(connectSocket); }); - + commit('setSocket', socket); }; - + const retryConnection = (reconnectFn) => { setTimeout(() => { console.log('Retrying Socket.io connection...'); reconnectFn(); }, 1000); // Retry every second }; - + connectSocket(); + } else { + console.log("User is not logged in or user data is not available."); } }, initializeDaemonSocket({ commit, state }) { if (state.isLoggedIn && state.user) { + let currentDaemonSocket = state.daemonSocket; const connectDaemonSocket = () => { + if (currentDaemonSocket) { + currentDaemonSocket.disconnect(); + } const daemonSocket = new WebSocket(import.meta.env.VITE_DAEMON_SOCKET); daemonSocket.onopen = () => { @@ -127,6 +138,7 @@ const store = createStore({ daemonSocket.onerror = (error) => { console.error('Daemon WebSocket error:', error); + console.log('WebSocket readyState:', daemonSocket.readyState); retryConnection(connectDaemonSocket); }; @@ -134,7 +146,6 @@ const store = createStore({ const message = event.data; console.log(message); if (message === "ping") { - console.log("Ping received, sending Pong..."); daemonSocket.send("pong"); } else { try { diff --git a/frontend/src/utils/knowledgeHelper.js b/frontend/src/utils/knowledgeHelper.js new file mode 100644 index 0000000..b640526 --- /dev/null +++ b/frontend/src/utils/knowledgeHelper.js @@ -0,0 +1,9 @@ +export function mapKnowledgeToText(value, t) { + if (value >= 90) return t('falukant.skillKnowledges.excelent'); + if (value >= 75) return t('falukant.skillKnowledges.veryhigh'); + if (value >= 60) return t('falukant.skillKnowledges.high'); + if (value >= 45) return t('falukant.skillKnowledges.medium'); + if (value >= 30) return t('falukant.skillKnowledges.low'); + if (value >= 15) return t('falukant.skillKnowledges.verylow'); + return t('falukant.skillKnowledges.none'); +} diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index 5f6f358..e25c396 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -1,160 +1,297 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/falukant/OverviewView.vue b/frontend/src/views/falukant/OverviewView.vue index 23b2d5d..659a61c 100644 --- a/frontend/src/views/falukant/OverviewView.vue +++ b/frontend/src/views/falukant/OverviewView.vue @@ -27,16 +27,56 @@

{{ $t('falukant.overview.productions.title') }}

+ + + + + + + + + + + + + + + + + +
{{ $t('falukant.branch.sale.region') }}{{ $t('falukant.branch.production.product') }}{{ $t('falukant.branch.production.quantity') }}{{ $t('falukant.branch.production.ending') }}
{{ production.cityName }}{{ $t(`falukant.product.${production.productName}`) }}{{ production.quantity }}{{ formatDate(production.endTimestamp) }}
+

{{ $t('falukant.branch.production.noProductions') }}

{{ $t('falukant.overview.stock.title') }}

+ + + + + + + + + + + + + + + +
{{ $t('falukant.branch.sale.region') }}{{ $t('falukant.branch.sale.product') }}{{ $t('falukant.branch.sale.quantity') }}
{{ item.regionName }}{{ $t(`falukant.product.${item.productLabelTr}`) }}{{ item.quantity }}
+

{{ $t('falukant.branch.sale.noInventory') }}

{{ $t('falukant.overview.branches.title') }}

- - - + + +
{{ branch.region.name }}{{ $t(`falukant.overview.branches.level.${branch.branchType.labelTr}`) }}
+ {{ branch.region.name }} + + {{ $t(`falukant.overview.branches.level.${branch.branchType.labelTr}`) }} +
@@ -48,8 +88,9 @@