diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index 865052a..b477085 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -7,11 +7,13 @@ class FalukantController { this.randomFirstName = this.randomFirstName.bind(this); this.randomLastName = this.randomLastName.bind(this); this.getInfo = this.getInfo.bind(this); + this.getInventory = this.getInventory.bind(this); + this.sellProduct = this.sellProduct.bind(this); } async getUser(req, res) { try { - const { userid: hashedUserId } = req.headers; + const { userid: hashedUserId } = req.headers; const result = await FalukantService.getUser(hashedUserId); res.status(200).json(result); } catch (error) { @@ -69,6 +71,94 @@ class FalukantController { res.status(500).json({ error: error.message }); } } + + async getBranch(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branch: branchId } = req.params; + console.log(branchId, req.params); + const result = await FalukantService.getBranch(hashedUserId, branchId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async createProduction(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId, productId, quantity } = req.body; + const result = await FalukantService.createProduction(hashedUserId, branchId, productId, quantity); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getProduction(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.params; + const result = await FalukantService.getProduction(hashedUserId, branchId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getStock(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.params || null; + const result = await FalukantService.getStock(hashedUserId, branchId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async createStock(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId, stockTypeId, stockSize } = req.body; + const result = await FalukantService.createStock(hashedUserId, branchId, stockTypeId, stockSize); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getProducts(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const result = await FalukantService.getProducts(hashedUserId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async getInventory(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId } = req.params; + const result = await FalukantService.getInventory(hashedUserId, branchId); + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } + + async sellProduct(req, res) { + try { + const { userid: hashedUserId } = req.headers; + const { branchId, productId, quality, quantity } = req.body; + const result = await FalukantService.sellProduct(hashedUserId, branchId, productId, quality, quantity); + res.status(201).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + } } export default FalukantController; diff --git a/backend/models/associations.js b/backend/models/associations.js index c5caf0d..ee2c846 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -42,6 +42,9 @@ import TitleOfNobility from './falukant/type/title_of_nobility.js'; import TitleRequirement from './falukant/type/title_requirement.js'; 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'; export default function setupAssociations() { @@ -205,21 +208,15 @@ export default function setupAssociations() { FalukantCharacter.belongsTo(TitleOfNobility, { foreignKey: 'titleOfNobility', as: 'nobleTitle' }); TitleOfNobility.hasMany(FalukantCharacter, { foreignKey: 'titleOfNobility', as: 'charactersWithNobleTitle' }); - + FalukantCharacter.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' }); RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' }); FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' }); FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' }); - FalukantStock.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' }); - FalukantUser.hasMany(FalukantStock, { foreignKey: 'userId', as: 'stocks' }); - - FalukantStock.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' }); - RegionData.hasMany(FalukantStock, { foreignKey: 'regionId', as: 'stocksInRegion' }); - - Knowledge.belongsTo(ProductType, { foreignKey: 'productTypeId', as: 'productType' }); - ProductType.hasMany(Knowledge, { foreignKey: 'productTypeId', as: 'knowledges' }); + Knowledge.belongsTo(ProductType, { foreignKey: 'productId', as: 'productType' }); + ProductType.hasMany(Knowledge, { foreignKey: 'productId', as: 'knowledges' }); Knowledge.belongsTo(FalukantCharacter, { foreignKey: 'characterId', as: 'character' }); FalukantCharacter.hasMany(Knowledge, { foreignKey: 'characterId', as: 'knowledges' }); @@ -235,4 +232,23 @@ export default function setupAssociations() { Branch.belongsTo(BranchType, { foreignKey: 'branchTypeId', as: 'branchType' }); BranchType.hasMany(Branch, { foreignKey: 'branchTypeId', as: 'branches' }); + + Production.belongsTo(Branch, { foreignKey: 'branchId', as: 'branch' }); + Branch.hasMany(Production, { foreignKey: 'branchId', as: 'productions' }); + + Production.belongsTo(ProductType, { foreignKey: 'productId', as: 'productType' }); + ProductType.hasMany(Production, { foreignKey: 'productId', as: 'productions' }); + + Inventory.belongsTo(FalukantStock, { foreignKey: 'stockId', as: 'stock' }); + FalukantStock.hasMany(Inventory, { foreignKey: 'stockId', as: 'inventories' }); + + Inventory.belongsTo(ProductType, { foreignKey: 'productId', as: 'productType' }); + ProductType.hasMany(Inventory, { foreignKey: 'productId', as: 'inventories' }); + + BuyableStock.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' }); + RegionData.hasMany(BuyableStock, { foreignKey: 'regionId', as: 'buyableStocks' }); + + Branch.hasMany(FalukantStock, { foreignKey: 'branchId', as: 'stocks' }); + FalukantStock.belongsTo(Branch, { foreignKey: 'branchId', as: 'branch' }); + } diff --git a/backend/models/falukant/data/buayble_stock.js b/backend/models/falukant/data/buayble_stock.js new file mode 100644 index 0000000..d2473f3 --- /dev/null +++ b/backend/models/falukant/data/buayble_stock.js @@ -0,0 +1,28 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class BuyableStock extends Model { } + +BuyableStock.init({ + regionId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + stockTypeId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + quantity: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}, { + sequelize, + modelName: 'BuyableStock', + tableName: 'buyable_stock', + schema: 'falukant_data', + timestamps: false, + underscored: true, +}); + +export default BuyableStock; diff --git a/backend/models/falukant/data/inventory.js b/backend/models/falukant/data/inventory.js new file mode 100644 index 0000000..08cf43d --- /dev/null +++ b/backend/models/falukant/data/inventory.js @@ -0,0 +1,37 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Inventory extends Model { } + +Inventory.init({ + stockId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + productId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + quantity: { + type: DataTypes.INTEGER, + allowNull: false, + }, + quality: { + type: DataTypes.INTEGER, + allowNull: false, + }, + producedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + } +}, { + sequelize, + modelName: 'Inventory', + tableName: 'inventory', + schema: 'falukant_data', + timestamps: false, + underscored: true, +}); + +export default Inventory; diff --git a/backend/models/falukant/data/production.js b/backend/models/falukant/data/production.js new file mode 100644 index 0000000..6651661 --- /dev/null +++ b/backend/models/falukant/data/production.js @@ -0,0 +1,33 @@ +import { Model, DataTypes } from 'sequelize'; +import { sequelize } from '../../../utils/sequelize.js'; + +class Production extends Model { } + +Production.init({ + branchId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + productId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + quantity: { + type: DataTypes.INTEGER, + allowNull: false, + }, + startTimestamp: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + } +}, { + sequelize, + modelName: 'Production', + tableName: 'production', + schema: 'falukant_data', + timestamps: false, + underscored: true, +}); + +export default Production; diff --git a/backend/models/falukant/data/stock.js b/backend/models/falukant/data/stock.js index 9ac8ffc..831e1b7 100644 --- a/backend/models/falukant/data/stock.js +++ b/backend/models/falukant/data/stock.js @@ -4,13 +4,10 @@ import { sequelize } from '../../../utils/sequelize.js'; class FalukantStock extends Model { } FalukantStock.init({ - userId: { - type: DataTypes.INTEGER, - allowNull: false, - }, - regionId: { + branchId: { type: DataTypes.INTEGER, allowNull: false, + defaultValue: 0 }, stockTypeId: { type: DataTypes.INTEGER, diff --git a/backend/models/falukant/data/user.js b/backend/models/falukant/data/user.js index d568b16..e4f3898 100644 --- a/backend/models/falukant/data/user.js +++ b/backend/models/falukant/data/user.js @@ -37,6 +37,11 @@ FalukantUser.init({ allowNull: false, defaultValue: 0.00, }, + certificate: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 1, + }, mainBranchRegionId: { type: DataTypes.INTEGER, allowNull: true, diff --git a/backend/models/index.js b/backend/models/index.js index 070bd81..5a0fd91 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -46,6 +46,9 @@ import TitleRequirement from './falukant/type/title_requirement.js'; import TitleOfNobility from './falukant/type/title_of_nobility.js'; 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'; const models = { SettingsType, @@ -54,10 +57,10 @@ const models = { UserRightType, User, UserParam, - Login, + Login, UserRight, InterestType, - InterestTranslationType, + InterestTranslationType, Interest, ContactMessage, UserParamVisibilityType, @@ -96,6 +99,9 @@ const models = { TitleRequirement, BranchType, Branch, + Production, + Inventory, + BuyableStock, }; export default models; diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 20ee292..4561c22 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -9,5 +9,13 @@ router.post('/user', falukantController.createUser); router.get('/name/randomfirstname/:gender', falukantController.randomFirstName); router.get('/name/randomlastname', falukantController.randomLastName); router.get('/info', falukantController.getInfo); +router.get('/branches/:branch', falukantController.getBranch); router.get('/branches', falukantController.getBranches); +router.post('/production', falukantController.createProduction); +router.get('/production/:branchId', falukantController.getProduction); +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', falukantController.sellProduct); export default router; diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 042df5c..c9a42f1 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -14,6 +14,12 @@ 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'; class FalukantService extends BaseService { async getFalukantUserByHashedId(hashedId) { @@ -26,7 +32,7 @@ class FalukantService extends BaseService { hashedId: hashedId }, } - ], + ], }); return falukantUser; } @@ -257,19 +263,19 @@ class FalukantService extends BaseService { { model: BranchType, as: 'branchType', - attributes: ['labelTr'], + attributes: ['labelTr'], }, { model: RegionData, as: 'region', - attributes: ['name'], + attributes: ['name'], } ], - attributes: ['id', 'regionId'], - order: [['branchTypeId', 'ASC']], + attributes: ['id', 'regionId'], + order: [['branchTypeId', 'ASC']], }); const enrichedBranches = branches.map(branch => ({ - ...branch.toJSON(), + ...branch.toJSON(), isMainBranch: falukantUser.mainBranchRegionId === branch.regionId, })); return enrichedBranches; @@ -279,6 +285,292 @@ class FalukantService extends BaseService { } } -} + 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'); + } + } + + 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'); + } + } + + 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'); + } + } + + 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; + } + + 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'); + } + } + + 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'); + } + } + + 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'); + } + } + + async sellProduct(hashedUserId, branchId, productId, quality, quantity) { + 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 product = await ProductType.findOne({ where: { id: productId } }); + if (!product) { + throw new Error('Product not found'); + } + const stock = await Stock.findOne({ where: { branchId: branch.id, } }); + if (!stock) { + throw new Error('Stock not found'); + } + 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'); + } + } +} export default new FalukantService(); diff --git a/backend/utils/falukant/initializeFalukantPredefines.js b/backend/utils/falukant/initializeFalukantPredefines.js index d9a4d99..fbc64dd 100644 --- a/backend/utils/falukant/initializeFalukantPredefines.js +++ b/backend/utils/falukant/initializeFalukantPredefines.js @@ -235,7 +235,7 @@ async function initializeFalukantProducts() { { labelTr: 'milk', category: 1, productionTime: 1, sellCost: 4 }, { labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 4 }, { labelTr: 'bread', category: 1, productionTime: 1, sellCost: 4 }, - { labelTr: 'beer', category: 1, productionTime: 1, sellCost: 4 }, + { labelTr: 'beer', category: 2, productionTime: 3, sellCost: 4 }, { labelTr: 'iron', category: 2, productionTime: 4, sellCost: 15 }, { labelTr: 'copper', category: 2, productionTime: 4, sellCost: 15 }, { labelTr: 'spices', category: 2, productionTime: 8, sellCost: 30 }, diff --git a/frontend/src/components/form/FormattedDropdown.vue b/frontend/src/components/form/FormattedDropdown.vue index 4daee1c..bd1657c 100644 --- a/frontend/src/components/form/FormattedDropdown.vue +++ b/frontend/src/components/form/FormattedDropdown.vue @@ -59,7 +59,6 @@ export default { watch: { modelValue(newVal) { this.selected = newVal; - console.log("FormattedDropdown modelValue changed:", newVal); }, }, methods: { diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index f13c4fc..74144f4 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -109,12 +109,35 @@ "info": "Informationen über den Direktor der Niederlassung." }, "sale": { - "title": "Verkauf", - "info": "Hier können Produkte verkauft werden." + "title": "Inventar", + "info": "Hier finden Sie eine Übersicht über die vorhandenen Produkte in der Filiale.", + "region": "Region", + "product": "Produkt", + "quality": "Qualität", + "quantity": "Menge", + "noInventory": "Kein Inventar verfügbar.", + "loadError": "Fehler beim Laden des Inventars.", + "sell": "Verkauf", + "sellButton": "Verkaufen", + "sellAllButton": "Alles verkaufen" }, "production": { "title": "Produktion", - "info": "Details zur Produktion in der Niederlassung." + "info": "Details zur Produktion in der Niederlassung.", + "selectProduct": "Produkt auswählen", + "quantity": "Menge", + "cost": "Kosten", + "duration": "Dauer", + "revenue": "Erlös", + "start": "Produktion starten", + "success": "Produktion erfolgreich gestartet!", + "error": "Fehler beim Starten der Produktion.", + "minutes": "Minuten", + "ending": "Abgeschlossen:", + "time": "Uhr", + "current": "Laufende Produktionen", + "product": "Produkt", + "remainingTime": "Verbleibende Zeit (Sekunden)" }, "columns": { "city": "Stadt", @@ -124,7 +147,53 @@ "production": "Produktion", "store": "Verkauf", "fullstack": "Produktion mit Verkauf" + }, + "revenue": { + "title": "Produkt-Erträge", + "product": "Produkt", + "absolute": "Erlös (absolut)", + "perMinute": "Erlös pro Minute", + "expand": "Erträge anzeigen", + "collapse": "Erträge ausblenden", + "knowledge": "Produktwissen" } + }, + "product": { + "wheat": "Weizen", + "grain": "Getreide", + "carrot": "Karotte", + "fish": "Fisch", + "meat": "Fleisch", + "leather": "Leder", + "wood": "Holz", + "stone": "Stein", + "milk": "Milch", + "cheese": "Käse", + "bread": "Brot", + "beer": "Bier", + "iron": "Eisen", + "copper": "Kupfer", + "spices": "Gewürze", + "salt": "Salz", + "sugar": "Zucker", + "vinegar": "Essig", + "cotton": "Baumwolle", + "wine": "Wein", + "gold": "Gold", + "diamond": "Diamant", + "furniture": "Möbel", + "clothing": "Kleidung", + "jewelry": "Schmuck", + "painting": "Gemälde", + "book": "Buch", + "weapon": "Waffe", + "armor": "Rüstung", + "shield": "Schild", + "horse": "Pferd", + "ox": "Ochse" + }, + "regionType": { + "city": "Stadt" } } } \ No newline at end of file diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 0391214..43b7301 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -12,6 +12,7 @@ const store = createStore({ language: navigator.language.startsWith('de') ? 'de' : 'en', menu: JSON.parse(localStorage.getItem('menu')) || [], socket: null, + daemonSocket: null, menuNeedsUpdate: false, }, mutations: { @@ -50,11 +51,21 @@ const store = createStore({ } state.socket = null; }, + setDaemonSocket(state, daemonSocket) { + state.daemonSocket = daemonSocket; + }, + clearDaemonSocket(state) { + if (state.daemonSocket) { + state.daemonSocket.disconnect(); + } + state.daemonSocket = null; + }, }, actions: { async login({ commit, dispatch }, user) { await commit('dologin', user); await dispatch('initializeSocket'); + await dispatch('initializeDaemonSocket'); const socket = this.getters.socket; if (socket) { socket.emit('setUserId', user.id); @@ -63,19 +74,89 @@ const store = createStore({ }, logout({ commit }) { commit('clearSocket'); + commit('clearDaemonSocket'); commit('dologout'); router.push('/'); }, initializeSocket({ commit, state }) { if (state.isLoggedIn && state.user) { - const socket = io(import.meta.env.VITE_API_BASE_URL); - socket.on('connect', () => { - socket.emit('setUserId', state.user.id); - }); - socket.on('disconnect', (reason) => { - console.warn('WebSocket disconnected:', reason); - }); - commit('setSocket', socket); + const connectSocket = () => { + 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.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(); + } + }, + initializeDaemonSocket({ commit, state }) { + if (state.isLoggedIn && state.user) { + const connectDaemonSocket = () => { + const daemonSocket = new WebSocket(import.meta.env.VITE_DAEMON_SOCKET); + + daemonSocket.onopen = () => { + console.log('Daemon WebSocket connected'); + const payload = JSON.stringify({ + event: 'setUserId', + data: { userId: state.user.id } + }); + daemonSocket.send(payload); + }; + + daemonSocket.onclose = (event) => { + console.warn('Daemon WebSocket disconnected:', event.reason); + retryConnection(connectDaemonSocket); + }; + + daemonSocket.onerror = (error) => { + console.error('Daemon WebSocket error:', error); + retryConnection(connectDaemonSocket); + }; + + daemonSocket.addEventListener('message', (event) => { + const message = event.data; + console.log(message); + if (message === "ping") { + console.log("Ping received, sending Pong..."); + daemonSocket.send("pong"); + } else { + try { + const data = JSON.parse(message); + console.log("Message received:", data); + } catch (error) { + console.error("Error parsing message:", error); + } + } + }); + + commit('setDaemonSocket', daemonSocket); + }; + + const retryConnection = (reconnectFn) => { + setTimeout(() => { + console.log('Retrying Daemon WebSocket connection...'); + reconnectFn(); + }, 1000); // Retry every second + }; + + connectDaemonSocket(); } }, setLanguage({ commit }, language) { @@ -97,6 +178,7 @@ const store = createStore({ language: state => state.language, menu: state => state.menu, socket: state => state.socket, + daemonSocket: state => state.daemonSocket, menuNeedsUpdate: state => state.menuNeedsUpdate, }, modules: { @@ -106,6 +188,7 @@ const store = createStore({ if (store.state.isLoggedIn && store.state.user) { store.dispatch('initializeSocket'); + store.dispatch('initializeDaemonSocket'); } export default store; diff --git a/frontend/src/views/falukant/BranchView.vue b/frontend/src/views/falukant/BranchView.vue index 344ef04..5f6f358 100644 --- a/frontend/src/views/falukant/BranchView.vue +++ b/frontend/src/views/falukant/BranchView.vue @@ -31,26 +31,150 @@

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

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

+
+ + + + + + + + + + + + + + + + + + + +
{{ $t('falukant.branch.sale.region') }}{{ $t('falukant.branch.sale.product') }}{{ $t('falukant.branch.sale.quality') }}{{ $t('falukant.branch.sale.quantity') }}{{ $t('falukant.branch.sale.sell') }}
{{ item.region.name }} ({{ $t(`falukant.regionType.${item.region.regionType.labelTr}`) + }}){{ $t(`falukant.product.${item.product.labelTr}`) }}{{ item.quality }}{{ item.totalQuantity }} + + +
+ +
+
+

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

+
-
+

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

-

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

+
+

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

+ + + + + + + + + + + + + + + + + +
{{ $t('falukant.branch.production.product') }}{{ $t('falukant.branch.production.quantity') }}{{ $t('falukant.branch.production.ending') }}{{ $t('falukant.branch.production.remainingTime') }}
{{ $t(`falukant.product.${production.productType.labelTr}`) }}{{ production.quantity }}{{ calculateEndDateTime(production.startTimestamp, + production.productType.productionTime) }}{{ calculateRemainingTime(production.startTimestamp, + production.productType.productionTime) }}
+
+ +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + +
{{ $t('falukant.branch.revenue.product') }}{{ $t('falukant.branch.revenue.knowledge') }}{{ $t('falukant.branch.revenue.absolute') }}{{ $t('falukant.branch.revenue.perMinute') }}
{{ $t(`falukant.product.${product.labelTr}`) }}{{ product.knowledges[0]?.knowledge }} %{{ calculateProductRevenue(product).absolute }}{{ calculateProductRevenue(product).perMinute }}
+
+ + @@ -127,4 +457,110 @@ export default { button { margin: 5px; } + +.production-section { + width: auto; +} + +.production-section label { + display: block; + margin-bottom: 5px; +} + +.production-section input[type="number"] { + width: 100px; +} + +.revenue-section { + border: 1px solid #ccc; + margin: 10px 0; + border-radius: 4px; + padding: 10px; +} + +.revenue-section h3 { + margin: 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.revenue-section button { + background: none; + border: none; + color: #007bff; + cursor: pointer; + font-size: 1em; + padding: 0; + margin: 0; + text-decoration: underline; +} + +.revenue-section button:hover { + color: #0056b3; +} + +.revenue-table { + margin-top: 10px; + overflow-x: auto; +} + +.revenue-table table { + width: 100%; + border-collapse: collapse; +} + +.revenue-table th, +.revenue-table td { + text-align: left; + padding: 8px; + border: 1px solid #ddd; +} + +.revenue-table th { + background-color: #f2f2f2; +} + +.production-section table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; +} + +.production-section th, +.production-section td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} + +.production-section th { + background-color: #f2f2f2; +} + +.inventory-table { + margin-top: 10px; + overflow-x: auto; +} + +.inventory-table table { + width: 100%; + border-collapse: collapse; +} + +.inventory-table th, +.inventory-table td { + text-align: left; + padding: 2px 3px; + border: 1px solid #ddd; +} + +.inventory-table td button { + margin: 0 0 0 5px; + padding: 2px; +} + +.inventory-table th { + background-color: #f2f2f2; +}