Füge neue Modelle für Produktion, Inventar und kaufbare Bestände hinzu; aktualisiere bestehende Modelle und Routen
This commit is contained in:
@@ -7,6 +7,8 @@ class FalukantController {
|
|||||||
this.randomFirstName = this.randomFirstName.bind(this);
|
this.randomFirstName = this.randomFirstName.bind(this);
|
||||||
this.randomLastName = this.randomLastName.bind(this);
|
this.randomLastName = this.randomLastName.bind(this);
|
||||||
this.getInfo = this.getInfo.bind(this);
|
this.getInfo = this.getInfo.bind(this);
|
||||||
|
this.getInventory = this.getInventory.bind(this);
|
||||||
|
this.sellProduct = this.sellProduct.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser(req, res) {
|
async getUser(req, res) {
|
||||||
@@ -69,6 +71,94 @@ class FalukantController {
|
|||||||
res.status(500).json({ error: error.message });
|
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;
|
export default FalukantController;
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ import TitleOfNobility from './falukant/type/title_of_nobility.js';
|
|||||||
import TitleRequirement from './falukant/type/title_requirement.js';
|
import TitleRequirement from './falukant/type/title_requirement.js';
|
||||||
import Branch from './falukant/data/branch.js';
|
import Branch from './falukant/data/branch.js';
|
||||||
import BranchType from './falukant/type/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() {
|
export default function setupAssociations() {
|
||||||
@@ -212,14 +215,8 @@ export default function setupAssociations() {
|
|||||||
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
|
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
|
||||||
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
|
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
|
||||||
|
|
||||||
FalukantStock.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
|
Knowledge.belongsTo(ProductType, { foreignKey: 'productId', as: 'productType' });
|
||||||
FalukantUser.hasMany(FalukantStock, { foreignKey: 'userId', as: 'stocks' });
|
ProductType.hasMany(Knowledge, { foreignKey: 'productId', as: 'knowledges' });
|
||||||
|
|
||||||
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(FalukantCharacter, { foreignKey: 'characterId', as: 'character' });
|
Knowledge.belongsTo(FalukantCharacter, { foreignKey: 'characterId', as: 'character' });
|
||||||
FalukantCharacter.hasMany(Knowledge, { foreignKey: 'characterId', as: 'knowledges' });
|
FalukantCharacter.hasMany(Knowledge, { foreignKey: 'characterId', as: 'knowledges' });
|
||||||
@@ -235,4 +232,23 @@ export default function setupAssociations() {
|
|||||||
|
|
||||||
Branch.belongsTo(BranchType, { foreignKey: 'branchTypeId', as: 'branchType' });
|
Branch.belongsTo(BranchType, { foreignKey: 'branchTypeId', as: 'branchType' });
|
||||||
BranchType.hasMany(Branch, { foreignKey: 'branchTypeId', as: 'branches' });
|
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' });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
backend/models/falukant/data/buayble_stock.js
Normal file
28
backend/models/falukant/data/buayble_stock.js
Normal file
@@ -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;
|
||||||
37
backend/models/falukant/data/inventory.js
Normal file
37
backend/models/falukant/data/inventory.js
Normal file
@@ -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;
|
||||||
33
backend/models/falukant/data/production.js
Normal file
33
backend/models/falukant/data/production.js
Normal file
@@ -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;
|
||||||
@@ -4,13 +4,10 @@ import { sequelize } from '../../../utils/sequelize.js';
|
|||||||
class FalukantStock extends Model { }
|
class FalukantStock extends Model { }
|
||||||
|
|
||||||
FalukantStock.init({
|
FalukantStock.init({
|
||||||
userId: {
|
branchId: {
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
regionId: {
|
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
stockTypeId: {
|
stockTypeId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ FalukantUser.init({
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0.00,
|
defaultValue: 0.00,
|
||||||
},
|
},
|
||||||
|
certificate: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 1,
|
||||||
|
},
|
||||||
mainBranchRegionId: {
|
mainBranchRegionId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ import TitleRequirement from './falukant/type/title_requirement.js';
|
|||||||
import TitleOfNobility from './falukant/type/title_of_nobility.js';
|
import TitleOfNobility from './falukant/type/title_of_nobility.js';
|
||||||
import BranchType from './falukant/type/branch.js';
|
import BranchType from './falukant/type/branch.js';
|
||||||
import Branch from './falukant/data/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 = {
|
const models = {
|
||||||
SettingsType,
|
SettingsType,
|
||||||
@@ -96,6 +99,9 @@ const models = {
|
|||||||
TitleRequirement,
|
TitleRequirement,
|
||||||
BranchType,
|
BranchType,
|
||||||
Branch,
|
Branch,
|
||||||
|
Production,
|
||||||
|
Inventory,
|
||||||
|
BuyableStock,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default models;
|
export default models;
|
||||||
|
|||||||
@@ -9,5 +9,13 @@ router.post('/user', falukantController.createUser);
|
|||||||
router.get('/name/randomfirstname/:gender', falukantController.randomFirstName);
|
router.get('/name/randomfirstname/:gender', falukantController.randomFirstName);
|
||||||
router.get('/name/randomlastname', falukantController.randomLastName);
|
router.get('/name/randomlastname', falukantController.randomLastName);
|
||||||
router.get('/info', falukantController.getInfo);
|
router.get('/info', falukantController.getInfo);
|
||||||
|
router.get('/branches/:branch', falukantController.getBranch);
|
||||||
router.get('/branches', falukantController.getBranches);
|
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;
|
export default router;
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ import { differenceInDays } from 'date-fns';
|
|||||||
import TitleOfNobility from '../models/falukant/type/title_of_nobility.js';
|
import TitleOfNobility from '../models/falukant/type/title_of_nobility.js';
|
||||||
import Branch from '../models/falukant/data/branch.js';
|
import Branch from '../models/falukant/data/branch.js';
|
||||||
import BranchType from '../models/falukant/type/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 {
|
class FalukantService extends BaseService {
|
||||||
async getFalukantUserByHashedId(hashedId) {
|
async getFalukantUserByHashedId(hashedId) {
|
||||||
@@ -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();
|
export default new FalukantService();
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ async function initializeFalukantProducts() {
|
|||||||
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 4 },
|
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 4 },
|
||||||
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 4 },
|
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 4 },
|
||||||
{ labelTr: 'bread', 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: 'iron', category: 2, productionTime: 4, sellCost: 15 },
|
||||||
{ labelTr: 'copper', category: 2, productionTime: 4, sellCost: 15 },
|
{ labelTr: 'copper', category: 2, productionTime: 4, sellCost: 15 },
|
||||||
{ labelTr: 'spices', category: 2, productionTime: 8, sellCost: 30 },
|
{ labelTr: 'spices', category: 2, productionTime: 8, sellCost: 30 },
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
modelValue(newVal) {
|
modelValue(newVal) {
|
||||||
this.selected = newVal;
|
this.selected = newVal;
|
||||||
console.log("FormattedDropdown modelValue changed:", newVal);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -109,12 +109,35 @@
|
|||||||
"info": "Informationen über den Direktor der Niederlassung."
|
"info": "Informationen über den Direktor der Niederlassung."
|
||||||
},
|
},
|
||||||
"sale": {
|
"sale": {
|
||||||
"title": "Verkauf",
|
"title": "Inventar",
|
||||||
"info": "Hier können Produkte verkauft werden."
|
"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": {
|
"production": {
|
||||||
"title": "Produktion",
|
"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": {
|
"columns": {
|
||||||
"city": "Stadt",
|
"city": "Stadt",
|
||||||
@@ -124,7 +147,53 @@
|
|||||||
"production": "Produktion",
|
"production": "Produktion",
|
||||||
"store": "Verkauf",
|
"store": "Verkauf",
|
||||||
"fullstack": "Produktion mit 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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ const store = createStore({
|
|||||||
language: navigator.language.startsWith('de') ? 'de' : 'en',
|
language: navigator.language.startsWith('de') ? 'de' : 'en',
|
||||||
menu: JSON.parse(localStorage.getItem('menu')) || [],
|
menu: JSON.parse(localStorage.getItem('menu')) || [],
|
||||||
socket: null,
|
socket: null,
|
||||||
|
daemonSocket: null,
|
||||||
menuNeedsUpdate: false,
|
menuNeedsUpdate: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
@@ -50,11 +51,21 @@ const store = createStore({
|
|||||||
}
|
}
|
||||||
state.socket = null;
|
state.socket = null;
|
||||||
},
|
},
|
||||||
|
setDaemonSocket(state, daemonSocket) {
|
||||||
|
state.daemonSocket = daemonSocket;
|
||||||
|
},
|
||||||
|
clearDaemonSocket(state) {
|
||||||
|
if (state.daemonSocket) {
|
||||||
|
state.daemonSocket.disconnect();
|
||||||
|
}
|
||||||
|
state.daemonSocket = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async login({ commit, dispatch }, user) {
|
async login({ commit, dispatch }, user) {
|
||||||
await commit('dologin', user);
|
await commit('dologin', user);
|
||||||
await dispatch('initializeSocket');
|
await dispatch('initializeSocket');
|
||||||
|
await dispatch('initializeDaemonSocket');
|
||||||
const socket = this.getters.socket;
|
const socket = this.getters.socket;
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.emit('setUserId', user.id);
|
socket.emit('setUserId', user.id);
|
||||||
@@ -63,19 +74,89 @@ const store = createStore({
|
|||||||
},
|
},
|
||||||
logout({ commit }) {
|
logout({ commit }) {
|
||||||
commit('clearSocket');
|
commit('clearSocket');
|
||||||
|
commit('clearDaemonSocket');
|
||||||
commit('dologout');
|
commit('dologout');
|
||||||
router.push('/');
|
router.push('/');
|
||||||
},
|
},
|
||||||
initializeSocket({ commit, state }) {
|
initializeSocket({ commit, state }) {
|
||||||
if (state.isLoggedIn && state.user) {
|
if (state.isLoggedIn && state.user) {
|
||||||
|
const connectSocket = () => {
|
||||||
const socket = io(import.meta.env.VITE_API_BASE_URL);
|
const socket = io(import.meta.env.VITE_API_BASE_URL);
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
|
console.log('Socket.io connected');
|
||||||
socket.emit('setUserId', state.user.id);
|
socket.emit('setUserId', state.user.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', (reason) => {
|
socket.on('disconnect', (reason) => {
|
||||||
console.warn('WebSocket disconnected:', reason);
|
console.warn('Socket.io disconnected:', reason);
|
||||||
|
retryConnection(connectSocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
commit('setSocket', socket);
|
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) {
|
setLanguage({ commit }, language) {
|
||||||
@@ -97,6 +178,7 @@ const store = createStore({
|
|||||||
language: state => state.language,
|
language: state => state.language,
|
||||||
menu: state => state.menu,
|
menu: state => state.menu,
|
||||||
socket: state => state.socket,
|
socket: state => state.socket,
|
||||||
|
daemonSocket: state => state.daemonSocket,
|
||||||
menuNeedsUpdate: state => state.menuNeedsUpdate,
|
menuNeedsUpdate: state => state.menuNeedsUpdate,
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
@@ -106,6 +188,7 @@ const store = createStore({
|
|||||||
|
|
||||||
if (store.state.isLoggedIn && store.state.user) {
|
if (store.state.isLoggedIn && store.state.user) {
|
||||||
store.dispatch('initializeSocket');
|
store.dispatch('initializeSocket');
|
||||||
|
store.dispatch('initializeDaemonSocket');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
|||||||
@@ -31,26 +31,150 @@
|
|||||||
<div class="sale-section">
|
<div class="sale-section">
|
||||||
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
|
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
|
||||||
<p>{{ $t('falukant.branch.sale.info') }}</p>
|
<p>{{ $t('falukant.branch.sale.info') }}</p>
|
||||||
|
<div class="inventory-table" v-if="inventory.length > 0">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.branch.sale.region') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.sale.product') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.sale.quality') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.sale.quantity') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.sale.sell') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(item, index) in inventory"
|
||||||
|
:key="`${item.region.id}-${item.product.id}-${item.quality}`">
|
||||||
|
<td>{{ item.region.name }} ({{ $t(`falukant.regionType.${item.region.regionType.labelTr}`)
|
||||||
|
}})</td>
|
||||||
|
<td>{{ $t(`falukant.product.${item.product.labelTr}`) }}</td>
|
||||||
|
<td>{{ item.quality }}</td>
|
||||||
|
<td>{{ item.totalQuantity }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="number" v-model.number="item.sellQuantity" :min="1"
|
||||||
|
:max="item.totalQuantity" :placeholder="item.totalQuantity" />
|
||||||
|
<button @click="sellItem(index)">{{ $t('falukant.branch.sale.sellButton') }}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button @click="sellAll">{{ $t('falukant.branch.sale.sellAllButton') }}</button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p>{{ $t('falukant.branch.sale.noInventory') }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Production Section -->
|
<!-- Production Section -->
|
||||||
<div class="production-section">
|
<div class="production-section" v-if="['fullstack', 'production'].includes(branchData?.branchType?.labelTr)">
|
||||||
<h3>{{ $t('falukant.branch.production.title') }}</h3>
|
<h3>{{ $t('falukant.branch.production.title') }}</h3>
|
||||||
<p>{{ $t('falukant.branch.production.info') }}</p>
|
<div v-if="this.productions?.length > 0">
|
||||||
|
<h4>{{ $t('falukant.branch.production.current') }}</h4>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.branch.production.product') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.production.quantity') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.production.ending') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.production.remainingTime') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="production in productions" :key="production.id">
|
||||||
|
<td>{{ $t(`falukant.product.${production.productType.labelTr}`) }}</td>
|
||||||
|
<td>{{ production.quantity }}</td>
|
||||||
|
<td>{{ calculateEndDateTime(production.startTimestamp,
|
||||||
|
production.productType.productionTime) }}</td>
|
||||||
|
<td>{{ calculateRemainingTime(production.startTimestamp,
|
||||||
|
production.productType.productionTime) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<template v-if="!this.productions || this.productions.length < 2">
|
||||||
|
<div>
|
||||||
|
<label for="product">{{ $t('falukant.branch.production.selectProduct') }}</label>
|
||||||
|
<select name="product" id="product" v-model="selectedProduct">
|
||||||
|
<option v-for="product in products" :key="product.id" :value="product.id">
|
||||||
|
{{ $t(`falukant.product.${product.labelTr}`) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="quantity">{{ $t('falukant.branch.production.quantity') }}</label>
|
||||||
|
<input type="number" id="quantity" v-model.number="productionQuantity" min="1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{{ $t('falukant.branch.production.cost') }}:
|
||||||
|
<strong>{{ calculateProductionCost() }}</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ $t('falukant.branch.production.duration') }}:
|
||||||
|
<strong>{{ calculateProductionDuration(selectedProduct?.id) }}</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ $t('falukant.branch.production.revenue') }}:
|
||||||
|
<strong>{{ calculateProductionRevenue() }}</strong>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button @click="startProduction" :disabled="!selectedProduct || productionQuantity < 1">
|
||||||
|
{{ $t('falukant.branch.production.start') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="revenue-section">
|
||||||
|
<h3>
|
||||||
|
<button @click="toggleRevenueTable">
|
||||||
|
{{ $t('falukant.branch.revenue.title') }}
|
||||||
|
{{ isRevenueTableOpen ? '▲' : '▼' }}
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
<div v-if="isRevenueTableOpen" class="revenue-table">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.branch.revenue.product') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.revenue.knowledge') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.revenue.absolute') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.revenue.perMinute') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="product in products" :key="product.id">
|
||||||
|
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||||
|
<td>{{ product.knowledges[0]?.knowledge }} %</td>
|
||||||
|
<td>{{ calculateProductRevenue(product).absolute }}</td>
|
||||||
|
<td>{{ calculateProductRevenue(product).perMinute }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<MessageDialog ref="messageDialog" />
|
||||||
|
<ErrorDialog ref="errorDialog" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||||
import FormattedDropdown from '@/components/form/FormattedDropdown.vue';
|
import FormattedDropdown from '@/components/form/FormattedDropdown.vue';
|
||||||
import apiClient from '@/utils/axios.js';
|
import apiClient from '@/utils/axios.js';
|
||||||
|
import MessageDialog from '@/dialogues/standard/MessageDialog.vue';
|
||||||
|
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue';
|
||||||
|
import { mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "BranchView",
|
name: "BranchView",
|
||||||
components: {
|
components: {
|
||||||
StatusBar,
|
StatusBar,
|
||||||
FormattedDropdown,
|
FormattedDropdown,
|
||||||
|
MessageDialog,
|
||||||
|
ErrorDialog,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["socket", "daemonSocket"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -60,19 +184,64 @@ export default {
|
|||||||
{ field: "cityName", label: this.$t('falukant.branch.columns.city') },
|
{ field: "cityName", label: this.$t('falukant.branch.columns.city') },
|
||||||
{ field: "type", label: this.$t('falukant.branch.columns.type') },
|
{ field: "type", label: this.$t('falukant.branch.columns.type') },
|
||||||
],
|
],
|
||||||
|
products: [],
|
||||||
|
branchData: null,
|
||||||
|
selectedProduct: null,
|
||||||
|
productionQuantity: 1,
|
||||||
|
isRevenueTableOpen: false,
|
||||||
|
inventory: [],
|
||||||
|
currentTime: Date.now(),
|
||||||
|
timer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.loadBranches();
|
await this.loadBranches();
|
||||||
const branchId = this.$route.params.branchId;
|
const branchId = this.$route.params.branchId;
|
||||||
console.log('route params:', this.$route.params, branchId);
|
const products = await apiClient.get('/api/falukant/products');
|
||||||
|
this.products = products.data;
|
||||||
|
console.log('products loaded');
|
||||||
if (branchId) {
|
if (branchId) {
|
||||||
console.log('branch selected');
|
|
||||||
this.selectedBranch = this.branches.find(branch => branch.id === parseInt(branchId)) || null;
|
this.selectedBranch = this.branches.find(branch => branch.id === parseInt(branchId)) || null;
|
||||||
} else {
|
} else {
|
||||||
console.log('main branch selected');
|
|
||||||
this.selectMainBranch();
|
this.selectMainBranch();
|
||||||
}
|
}
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.on("falukantBranchUpdate", this.fetchBranch);
|
||||||
|
}
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.addEventListener('message', (event) => {
|
||||||
|
try {
|
||||||
|
if (event.data === "ping") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
console.log('Daemon WebSocket message received in BranchView:', message);
|
||||||
|
|
||||||
|
if (message.event === 'production_ready' && message.branch_id === this.selectedBranch?.id) {
|
||||||
|
this.handleProductionReadyEvent(message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing WebSocket message in BranchView:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Daemon socket is not initialized.');
|
||||||
|
}
|
||||||
|
this.startTimer();
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.stopTimer();
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.off("falukantBranchUpdate", this.fetchBranch);
|
||||||
|
}
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.onmessage = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
async selectedBranch(newBranch) {
|
||||||
|
await this.loadBranchData(newBranch);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadBranches() {
|
async loadBranches() {
|
||||||
@@ -84,7 +253,6 @@ export default {
|
|||||||
type: this.$t(`falukant.branch.types.${branch.branchType.labelTr}`),
|
type: this.$t(`falukant.branch.types.${branch.branchType.labelTr}`),
|
||||||
isMainBranch: branch.isMainBranch,
|
isMainBranch: branch.isMainBranch,
|
||||||
}));
|
}));
|
||||||
// Wenn keine selectedBranch gesetzt ist, versuche die Main Branch zu wählen
|
|
||||||
if (!this.selectedBranch) {
|
if (!this.selectedBranch) {
|
||||||
this.selectMainBranch();
|
this.selectMainBranch();
|
||||||
}
|
}
|
||||||
@@ -92,11 +260,16 @@ export default {
|
|||||||
console.error('Error loading branches:', error);
|
console.error('Error loading branches:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async loadBranchData(newBranch) {
|
||||||
|
if (newBranch) {
|
||||||
|
this.getBranchData(newBranch);
|
||||||
|
await this.loadInventory();
|
||||||
|
}
|
||||||
|
},
|
||||||
selectMainBranch() {
|
selectMainBranch() {
|
||||||
const main = this.branches.find(b => b.isMainBranch) || null;
|
const main = this.branches.find(b => b.isMainBranch) || null;
|
||||||
if (main !== this.selectedBranch) {
|
if (main !== this.selectedBranch) {
|
||||||
this.selectedBranch = main;
|
this.selectedBranch = main;
|
||||||
console.log("Main branch selected:", this.selectedBranch);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createBranch() {
|
createBranch() {
|
||||||
@@ -109,6 +282,163 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
calculateProductionCost() {
|
||||||
|
if (!this.products) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||||
|
if (product) {
|
||||||
|
return 7 * product.category * this.productionQuantity;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
calculateProductionDuration(productId) {
|
||||||
|
if (!this.products || !productId) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const product = this.products.find(p => p.id === productId);
|
||||||
|
if (!product) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalMinutes = product.productionTime * 60;
|
||||||
|
const decimalTime = (totalMinutes / 60).toFixed(2);
|
||||||
|
|
||||||
|
return decimalTime;
|
||||||
|
},
|
||||||
|
async startProduction() {
|
||||||
|
if (this.selectedBranch && this.selectedProduct && this.productionQuantity > 0) {
|
||||||
|
try {
|
||||||
|
await apiClient.post(`/api/falukant/production`, {
|
||||||
|
branchId: this.selectedBranch.id,
|
||||||
|
productId: this.selectedProduct,
|
||||||
|
quantity: this.productionQuantity,
|
||||||
|
});
|
||||||
|
this.$root.$refs.messageDialog.open(this.$t('falukant.branch.production.success'));
|
||||||
|
} catch (error) {
|
||||||
|
this.$root.$refs.errorDialog.open(this.$t(`falukant.branch.production.error${error.response.data.error}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateProductionRevenue() {
|
||||||
|
if (!this.selectedProduct) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!this.products) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||||
|
if (!product) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const productRevenue = this.calculateProductRevenue(product);
|
||||||
|
return productRevenue.absolute;
|
||||||
|
},
|
||||||
|
toggleRevenueTable() {
|
||||||
|
this.isRevenueTableOpen = !this.isRevenueTableOpen;
|
||||||
|
},
|
||||||
|
calculateProductRevenue(product) {
|
||||||
|
if (!product.knowledges || product.knowledges.length === 0) {
|
||||||
|
return { absolute: 0, perMinute: 0 };
|
||||||
|
}
|
||||||
|
const knowledgeFactor = product.knowledges[0]?.knowledge || 0;
|
||||||
|
const maxPrice = product.category * 9;
|
||||||
|
const minPrice = maxPrice * 0.6;
|
||||||
|
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||||
|
const productionTimeInMinutes = product.productionTime;
|
||||||
|
const perMinute = (revenuePerUnit / productionTimeInMinutes).toFixed(2);
|
||||||
|
return {
|
||||||
|
absolute: revenuePerUnit.toFixed(2),
|
||||||
|
perMinute: perMinute,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async fetchBranch(data) {
|
||||||
|
try {
|
||||||
|
if (data.branchId === this.selectedBranch.id) {
|
||||||
|
this.getBranchData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getBranchData(newBranch = null) {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/api/falukant/branches/${newBranch ? newBranch.id : this.selectedBranch.id}`);
|
||||||
|
this.branchData = response.data;
|
||||||
|
this.director = response.data.director;
|
||||||
|
this.productions = response.data.productions.sort((a, b) => {
|
||||||
|
const endTimeA = new Date(a.startTimestamp).getTime() + a.productType.productionTime * 60 * 1000;
|
||||||
|
const endTimeB = new Date(b.startTimestamp).getTime() + b.productType.productionTime * 60 * 1000;
|
||||||
|
return endTimeA - endTimeB;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateEndDateTime(startTimestamp, productionTime) {
|
||||||
|
const startTime = new Date(startTimestamp);
|
||||||
|
const endTime = new Date(startTime.getTime() + productionTime * 60 * 1000);
|
||||||
|
return endTime.toLocaleString(); // Datum und Uhrzeit
|
||||||
|
},
|
||||||
|
calculateRemainingTime(startTimestamp, productionTime) {
|
||||||
|
const startTime = new Date(startTimestamp).getTime();
|
||||||
|
const endTime = startTime + productionTime * 60 * 1000;
|
||||||
|
const now = Date.now();
|
||||||
|
const remainingSeconds = Math.max(Math.floor((endTime - now) / 1000), 0);
|
||||||
|
return remainingSeconds; // Verbleibende Zeit in Sekunden
|
||||||
|
},
|
||||||
|
startTimer() {
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.$forceUpdate(); // Aktualisiert die verbleibende Zeit in der Tabelle
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
stopTimer() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sellItem(index) {
|
||||||
|
const item = this.inventory[index];
|
||||||
|
console.log(`Selling ${item.sellQuantity || item.totalQuantity} of ${item.product.labelTr}`);
|
||||||
|
const quantityToSell = item.sellQuantity || item.totalQuantity;
|
||||||
|
item.totalQuantity -= quantityToSell;
|
||||||
|
if (item.totalQuantity <= 0) {
|
||||||
|
this.inventory.splice(index, 1);
|
||||||
|
}
|
||||||
|
item.sellQuantity = null;
|
||||||
|
},
|
||||||
|
sellAll() {
|
||||||
|
console.log('Selling all items in inventory');
|
||||||
|
this.inventory.forEach(item => {
|
||||||
|
console.log(`Selling ${item.totalQuantity} of ${item.product.labelTr}`);
|
||||||
|
});
|
||||||
|
this.inventory = []; // Leert das Inventar
|
||||||
|
},
|
||||||
|
async loadInventory() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/api/falukant/inventory/${this.selectedBranch?.id}`, {});
|
||||||
|
this.inventory = response.data.map(item => ({
|
||||||
|
...item,
|
||||||
|
sellQuantity: item.totalQuantity, // Voreinstellung
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading inventory:', error);
|
||||||
|
this.$refs.errorDialog.open(this.$t('falukant.branch.sale.loadError'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleProductionReadyEvent(message) {
|
||||||
|
try {
|
||||||
|
console.log('Production ready event received:', message);
|
||||||
|
if (message.branch_id === this.selectedBranch?.id) {
|
||||||
|
await this.loadBranchData(this.selectedBranch);
|
||||||
|
await this.loadInventory();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing production_ready event:', error);
|
||||||
|
console.error(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -127,4 +457,110 @@ export default {
|
|||||||
button {
|
button {
|
||||||
margin: 5px;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user