Falukant production, family and administration enhancements
@@ -89,6 +89,43 @@ class AdminController {
|
|||||||
res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' });
|
res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchUser(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: userId } = req.headers;
|
||||||
|
const { userName, characterName } = req.body;
|
||||||
|
const response = await AdminService.getFalukantUser(userId, userName, characterName);
|
||||||
|
res.status(200).json(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(403).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFalukantUserById(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: userId } = req.headers;
|
||||||
|
const { id: hashedId } = req.params;
|
||||||
|
const response = await AdminService.getFalukantUserById(userId, hashedId);
|
||||||
|
res.status(200).json(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(403).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async changeFalukantUser(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: userId } = req.headers;
|
||||||
|
const data = req.body;
|
||||||
|
const { id: falukantUserId, } = req.body;
|
||||||
|
const response = await AdminService.changeFalukantUser(userId, falukantUserId, data);
|
||||||
|
res.status(200).json(response);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(403).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminController;
|
export default AdminController;
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ class FalukantController {
|
|||||||
this.convertProposalToDirector = this.convertProposalToDirector.bind(this);
|
this.convertProposalToDirector = this.convertProposalToDirector.bind(this);
|
||||||
this.getDirectorForBranch = this.getDirectorForBranch.bind(this);
|
this.getDirectorForBranch = this.getDirectorForBranch.bind(this);
|
||||||
this.setSetting = this.setSetting.bind(this);
|
this.setSetting = this.setSetting.bind(this);
|
||||||
this.getMarriageProposals = this.getMarriageProposals.bind(this);
|
this.getFamily = this.getFamily.bind(this);
|
||||||
|
this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this);
|
||||||
|
this.getGifts = this.getGifts.bind(this);
|
||||||
|
this.sendGift = this.sendGift.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser(req, res) {
|
async getUser(req, res) {
|
||||||
@@ -308,9 +311,6 @@ class FalukantController {
|
|||||||
const { userid: hashedUserId } = req.headers;
|
const { userid: hashedUserId } = req.headers;
|
||||||
const { branchId } = req.params;
|
const { branchId } = req.params;
|
||||||
const result = await FalukantService.getDirectorForBranch(hashedUserId, branchId);
|
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);
|
res.status(200).json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
@@ -329,13 +329,74 @@ class FalukantController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMarriageProposals(req, res) {
|
async getFamily(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userid: hashedUserId } = req.headers;
|
const { userid: hashedUserId } = req.headers;
|
||||||
const result = await FalukantService.getMarriageProposals(hashedUserId);
|
const result = await FalukantService.getFamily(hashedUserId);
|
||||||
|
if (!result) {
|
||||||
|
res.status(404).json({ error: 'No family data found' });
|
||||||
|
}
|
||||||
res.status(200).json(result);
|
res.status(200).json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptMarriageProposal(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: hashedUserId } = req.headers;
|
||||||
|
const { proposalId } = req.body;
|
||||||
|
const result = await FalukantService.acceptMarriageProposal(hashedUserId, proposalId);
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGifts(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: hashedUserId } = req.headers;
|
||||||
|
const result = await FalukantService.getGifts(hashedUserId);
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendGift(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: hashedUserId } = req.headers;
|
||||||
|
const { giftId} = req.body;
|
||||||
|
const result = await FalukantService.sendGift(hashedUserId, giftId);
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTitelsOfNobility(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: hashedUserId } = req.headers;
|
||||||
|
const result = await FalukantService.getTitlesOfNobility(hashedUserId);
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHouseTypes(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: hashedUserId } = req.headers;
|
||||||
|
const result = await FalukantService.getHouseTypes(hashedUserId);
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,10 +93,6 @@ const menuStructure = {
|
|||||||
visible: ["hasfalukantaccount"],
|
visible: ["hasfalukantaccount"],
|
||||||
path: "/falukant/directors"
|
path: "/falukant/directors"
|
||||||
},
|
},
|
||||||
factory: {
|
|
||||||
visible: ["hasfalukantaccount"],
|
|
||||||
path: "/falukant/factory"
|
|
||||||
},
|
|
||||||
family: {
|
family: {
|
||||||
visible: ["hasfalukantaccount"],
|
visible: ["hasfalukantaccount"],
|
||||||
path: "/falukant/family"
|
path: "/falukant/family"
|
||||||
|
|||||||
@@ -52,13 +52,16 @@ import TownProductWorth from './falukant/data/town_product_worth.js';
|
|||||||
import DayProduction from './falukant/log/dayproduction.js';
|
import DayProduction from './falukant/log/dayproduction.js';
|
||||||
import DaySell from './falukant/log/daysell.js';
|
import DaySell from './falukant/log/daysell.js';
|
||||||
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
||||||
import Notification from './falukant/log/notification';
|
import Notification from './falukant/log/notification.js';
|
||||||
import CharacterTrait from './falukant/type/character_trait.js';
|
import CharacterTrait from './falukant/type/character_trait.js';
|
||||||
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
||||||
import Mood from './falukant/type/mood.js';
|
import Mood from './falukant/type/mood.js';
|
||||||
import PromotionalGift from './falukant/type/promotional_gift.js';
|
import PromotionalGift from './falukant/type/promotional_gift.js';
|
||||||
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
||||||
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
|
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
|
||||||
|
import RelationshipType from './falukant/type/relationship.js';
|
||||||
|
import Relationship from './falukant/data/relationship.js';
|
||||||
|
import PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
||||||
|
|
||||||
export default function setupAssociations() {
|
export default function setupAssociations() {
|
||||||
// UserParam related associations
|
// UserParam related associations
|
||||||
@@ -326,4 +329,27 @@ export default function setupAssociations() {
|
|||||||
|
|
||||||
PromotionalGift.belongsToMany(Mood, {through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods',});
|
PromotionalGift.belongsToMany(Mood, {through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods',});
|
||||||
Mood.belongsToMany(PromotionalGift, {through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts',});
|
Mood.belongsToMany(PromotionalGift, {through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts',});
|
||||||
|
|
||||||
|
Relationship.belongsTo(RelationshipType, { foreignKey: 'relationshipTypeId', as: 'relationshipType' });
|
||||||
|
RelationshipType.hasMany(Relationship, { foreignKey: 'relationshipTypeId', as: 'relationships' });
|
||||||
|
|
||||||
|
Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character1Id', as: 'character1', });
|
||||||
|
Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character2Id', as: 'character2', });
|
||||||
|
FalukantCharacter.hasMany(Relationship, {foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', });
|
||||||
|
FalukantCharacter.hasMany(Relationship, {foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', });
|
||||||
|
|
||||||
|
PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' });
|
||||||
|
PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' });
|
||||||
|
|
||||||
|
PromotionalGiftLog.belongsTo(FalukantCharacter, { foreignKey: 'senderCharacterId', as: 'character' });
|
||||||
|
FalukantCharacter.hasMany(PromotionalGiftLog, { foreignKey: 'senderCharacterId', as: 'logs' });
|
||||||
|
|
||||||
|
PromotionalGiftLog.belongsTo(FalukantCharacter, { foreignKey: 'recipientCharacterId', as: 'recipient' });
|
||||||
|
FalukantCharacter.hasMany(PromotionalGiftLog, { foreignKey: 'recipientCharacterId', as: 'giftlogs' });
|
||||||
|
|
||||||
|
PromotionalGift.hasMany(PromotionalGiftCharacterTrait, { foreignKey: 'gift_id', as: 'characterTraits' });
|
||||||
|
PromotionalGift.hasMany(PromotionalGiftMood, { foreignKey: 'gift_id', as: 'promotionalgiftmoods' });
|
||||||
|
|
||||||
|
PromotionalGiftCharacterTrait.belongsTo(PromotionalGift, { foreignKey: 'gift_id', as: 'promotionalgiftcharactertrait' });
|
||||||
|
PromotionalGiftMood.belongsTo(PromotionalGift, { foreignKey: 'gift_id', as: 'promotionalgiftcharactermood' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ class FalukantCharacter extends Model {}
|
|||||||
|
|
||||||
FalukantCharacter.init(
|
FalukantCharacter.init(
|
||||||
{
|
{
|
||||||
user_id: {
|
userId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
region_id: {
|
regionId: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
first_name: {
|
firstName: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
last_name: {
|
lastName: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ Director.init({
|
|||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
lastSalaryPayout: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: new Date(0)
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
sequelize,
|
sequelize,
|
||||||
|
|||||||
56
backend/models/falukant/data/relationship.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Model, DataTypes } from 'sequelize';
|
||||||
|
import { sequelize } from '../../../utils/sequelize.js';
|
||||||
|
import FalukantCharacter from './character.js';
|
||||||
|
|
||||||
|
class Relationship extends Model {}
|
||||||
|
|
||||||
|
Relationship.init(
|
||||||
|
{
|
||||||
|
character1Id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
references: {
|
||||||
|
model: FalukantCharacter,
|
||||||
|
key: 'id',
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
character2Id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
references: {
|
||||||
|
model: FalukantCharacter,
|
||||||
|
key: 'id',
|
||||||
|
},
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
relationshipTypeId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
widowFirstName1: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
widowFirstName2: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
nextStepProgress: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
modelName: 'Relationship',
|
||||||
|
tableName: 'relationship',
|
||||||
|
schema: 'falukant_data',
|
||||||
|
timestamps: true,
|
||||||
|
underscored: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Relationship;
|
||||||
@@ -24,6 +24,11 @@ DayProduction.init({
|
|||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
|
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
|
||||||
|
},
|
||||||
|
productionDate: {
|
||||||
|
type: DataTypes.DATEONLY,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: sequelize.literal('CURRENT_DATE'),
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
sequelize,
|
sequelize,
|
||||||
@@ -35,10 +40,9 @@ DayProduction.init({
|
|||||||
indexes: [
|
indexes: [
|
||||||
{
|
{
|
||||||
unique: true,
|
unique: true,
|
||||||
fields: ['producer_id', 'product_id', 'region_id']
|
fields: ['producer_id', 'product_id', 'region_id', 'production_date']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default DayProduction;
|
export default DayProduction;
|
||||||
|
|||||||
32
backend/models/falukant/log/promotional_gift.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Model, DataTypes } from 'sequelize';
|
||||||
|
import { sequelize } from '../../../utils/sequelize.js';
|
||||||
|
|
||||||
|
class PromotionalGiftLog extends Model { };
|
||||||
|
|
||||||
|
PromotionalGiftLog.init({
|
||||||
|
senderCharacterId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
recipientCharacterId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
giftId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
changeValue: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'PromotionalGiftLog',
|
||||||
|
tableName: 'promotional_gift',
|
||||||
|
schema: 'falukant_log',
|
||||||
|
timestamps: true,
|
||||||
|
underscored: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PromotionalGiftLog;
|
||||||
@@ -16,7 +16,7 @@ PromotionalGift.init(
|
|||||||
value: {
|
value: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0, // Wert des Geschenks
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Model, DataTypes } from 'sequelize';
|
import { Model, DataTypes } from 'sequelize';
|
||||||
import { sequelize } from '../../../utils/sequelize.js';
|
import { sequelize } from '../../../utils/sequelize.js';
|
||||||
|
|
||||||
class Relationship extends Model {}
|
class RelationshipType extends Model {}
|
||||||
|
|
||||||
Relationship.init(
|
RelationshipType.init(
|
||||||
{
|
{
|
||||||
tr: {
|
tr: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
@@ -12,7 +12,7 @@ Relationship.init(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sequelize,
|
sequelize,
|
||||||
modelName: 'Relationship',
|
modelName: 'RelationshipType',
|
||||||
tableName: 'relationship',
|
tableName: 'relationship',
|
||||||
schema: 'falukant_type',
|
schema: 'falukant_type',
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
@@ -20,4 +20,4 @@ Relationship.init(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Relationship;
|
export default RelationshipType;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ FalukantStockType.init({
|
|||||||
labelTr: {
|
labelTr: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
},
|
},
|
||||||
cost: {
|
cost: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
|
|||||||
@@ -55,15 +55,17 @@ import DirectorProposal from './falukant/data/director_proposal.js';
|
|||||||
import TownProductWorth from './falukant/data/town_product_worth.js';
|
import TownProductWorth from './falukant/data/town_product_worth.js';
|
||||||
import DayProduction from './falukant/log/dayproduction.js';
|
import DayProduction from './falukant/log/dayproduction.js';
|
||||||
import DaySell from './falukant/log/daysell.js';
|
import DaySell from './falukant/log/daysell.js';
|
||||||
import Notification from './falukant/log/notification';
|
import Notification from './falukant/log/notification.js';
|
||||||
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
||||||
import Relationship from './falukant/type/relationship.js';
|
import RelationshipType from './falukant/type/relationship.js';
|
||||||
import CharacterTrait from './falukant/type/character_trait.js';
|
import CharacterTrait from './falukant/type/character_trait.js';
|
||||||
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
||||||
import Mood from './falukant/type/mood.js';
|
import Mood from './falukant/type/mood.js';
|
||||||
import PromotionalGift from './falukant/type/promotional_gift.js';
|
import PromotionalGift from './falukant/type/promotional_gift.js';
|
||||||
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
||||||
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
|
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
|
||||||
|
import Relationship from './falukant/data/relationship.js';
|
||||||
|
import PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
||||||
|
|
||||||
const models = {
|
const models = {
|
||||||
SettingsType,
|
SettingsType,
|
||||||
@@ -125,6 +127,7 @@ const models = {
|
|||||||
DaySell,
|
DaySell,
|
||||||
Notification,
|
Notification,
|
||||||
MarriageProposal,
|
MarriageProposal,
|
||||||
|
RelationshipType,
|
||||||
Relationship,
|
Relationship,
|
||||||
CharacterTrait,
|
CharacterTrait,
|
||||||
FalukantCharacterTrait,
|
FalukantCharacterTrait,
|
||||||
@@ -132,6 +135,7 @@ const models = {
|
|||||||
PromotionalGift,
|
PromotionalGift,
|
||||||
PromotionalGiftCharacterTrait,
|
PromotionalGiftCharacterTrait,
|
||||||
PromotionalGiftMood,
|
PromotionalGiftMood,
|
||||||
|
PromotionalGiftLog,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default models;
|
export default models;
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ export async function createTriggers() {
|
|||||||
await sequelize.query(createKnowledgeTriggerMethod);
|
await sequelize.query(createKnowledgeTriggerMethod);
|
||||||
await sequelize.query(createKnowledgeTrigger);
|
await sequelize.query(createKnowledgeTrigger);
|
||||||
await sequelize.query(updateMoney);
|
await sequelize.query(updateMoney);
|
||||||
|
await initializeCharacterTraitTrigger();
|
||||||
|
|
||||||
console.log('Triggers created successfully');
|
console.log('Triggers created successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -200,3 +201,53 @@ export async function createTriggers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const initializeCharacterTraitTrigger = async () => {
|
||||||
|
try {
|
||||||
|
const triggerCheckQuery = `
|
||||||
|
SELECT tgname
|
||||||
|
FROM pg_trigger
|
||||||
|
WHERE tgname = 'trigger_assign_traits';
|
||||||
|
`;
|
||||||
|
const [existingTrigger] = await sequelize.query(triggerCheckQuery, { type: sequelize.QueryTypes.SELECT });
|
||||||
|
if (!existingTrigger) {
|
||||||
|
console.log('⚡ Erstelle den Trigger für zufällige Traits...');
|
||||||
|
const createTriggerFunctionQuery = `
|
||||||
|
CREATE OR REPLACE FUNCTION falukant_data.assign_random_traits()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
trait_ids INTEGER[];
|
||||||
|
i INTEGER;
|
||||||
|
BEGIN
|
||||||
|
-- Zufällig 5 Trait-IDs auswählen
|
||||||
|
SELECT ARRAY(
|
||||||
|
SELECT id FROM falukant_type.character_trait
|
||||||
|
ORDER BY RANDOM()
|
||||||
|
LIMIT 5
|
||||||
|
) INTO trait_ids;
|
||||||
|
|
||||||
|
-- Die 5 Traits dem neuen Charakter zuweisen
|
||||||
|
FOR i IN 1..array_length(trait_ids, 1) LOOP
|
||||||
|
INSERT INTO falukant_data.falukant_character_trait (character_id, trait_id)
|
||||||
|
VALUES (NEW.id, trait_ids[i]);
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`;
|
||||||
|
const createTriggerQuery = `
|
||||||
|
CREATE TRIGGER trigger_assign_traits
|
||||||
|
AFTER INSERT ON falukant_data.character
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION falukant_data.assign_random_traits();
|
||||||
|
`;
|
||||||
|
await sequelize.query(createTriggerFunctionQuery);
|
||||||
|
await sequelize.query(createTriggerQuery);
|
||||||
|
console.log('✅ Trigger erfolgreich erstellt.');
|
||||||
|
} else {
|
||||||
|
console.log('🔹 Trigger existiert bereits. Keine Aktion erforderlich.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Fehler beim Erstellen des Triggers:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@@ -11,5 +11,8 @@ router.post('/interest/translation', authenticate, adminController.changeTransla
|
|||||||
router.delete('/interest/:id', authenticate, adminController.deleteInterest);
|
router.delete('/interest/:id', authenticate, adminController.deleteInterest);
|
||||||
router.get('/opencontacts', authenticate, adminController.getOpenContacts);
|
router.get('/opencontacts', authenticate, adminController.getOpenContacts);
|
||||||
router.post('/contacts/answer', authenticate, adminController.answerContact);
|
router.post('/contacts/answer', authenticate, adminController.answerContact);
|
||||||
|
router.post('/falukant/searchuser', authenticate, adminController.searchUser);
|
||||||
|
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
|
||||||
|
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -30,5 +30,11 @@ router.post('/director/proposal', falukantController.getDirectorProposals);
|
|||||||
router.post('/director/convertproposal', falukantController.convertProposalToDirector);
|
router.post('/director/convertproposal', falukantController.convertProposalToDirector);
|
||||||
router.post('/director/settings', falukantController.setSetting);
|
router.post('/director/settings', falukantController.setSetting);
|
||||||
router.get('/director/:branchId', falukantController.getDirectorForBranch);
|
router.get('/director/:branchId', falukantController.getDirectorForBranch);
|
||||||
router.get('/marriage/proposals', falukantController.getMarriageProposals);
|
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
||||||
|
router.get('/family/gifts', falukantController.getGifts);
|
||||||
|
router.post('/family/gift', falukantController.sendGift);
|
||||||
|
router.get('/family', falukantController.getFamily);
|
||||||
|
router.get('/nobility/titels', falukantController.getTitelsOfNobility);
|
||||||
|
router.get('/houses/types', falukantController.getHouseTypes);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import UserParamValue from "../models/type/user_param_value.js";
|
|||||||
import ContactMessage from "../models/service/contactmessage.js";
|
import ContactMessage from "../models/service/contactmessage.js";
|
||||||
import ContactService from "./ContactService.js";
|
import ContactService from "./ContactService.js";
|
||||||
import { sendAnswerEmail } from './emailService.js';
|
import { sendAnswerEmail } from './emailService.js';
|
||||||
|
import { Op } from 'sequelize';
|
||||||
|
import FalukantUser from "../models/falukant/data/user.js";
|
||||||
|
import FalukantCharacter from "../models/falukant/data/character.js";
|
||||||
|
import FalukantPredefineFirstname from "../models/falukant/predefine/firstname.js";
|
||||||
|
import FalukantPredefineLastname from "../models/falukant/predefine/lastname.js";
|
||||||
|
|
||||||
class AdminService {
|
class AdminService {
|
||||||
async hasUserAccess(userId, section) {
|
async hasUserAccess(userId, section) {
|
||||||
@@ -138,6 +143,149 @@ class AdminService {
|
|||||||
await sendAnswerEmail(contact.email, answer, contact.language || 'en');
|
await sendAnswerEmail(contact.email, answer, contact.language || 'en');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFalukantUser(userId, userName, characterName) {
|
||||||
|
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||||
|
throw new Error('noaccess');
|
||||||
|
}
|
||||||
|
let users;
|
||||||
|
if (userName) {
|
||||||
|
users = await User.findAll({
|
||||||
|
where: {
|
||||||
|
username: {
|
||||||
|
[Op.like]: '%' + userName + '%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: [{
|
||||||
|
model: FalukantUser,
|
||||||
|
as: 'falukantData',
|
||||||
|
required: true,
|
||||||
|
include: [{
|
||||||
|
model: FalukantCharacter,
|
||||||
|
as: 'character',
|
||||||
|
required: true,
|
||||||
|
include: [{
|
||||||
|
model: FalukantPredefineFirstname,
|
||||||
|
as: 'definedFirstName',
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
model: FalukantPredefineLastname,
|
||||||
|
as: 'definedLastName',
|
||||||
|
required: true
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else if (characterName) {
|
||||||
|
const [firstname, lastname] = characterName.split(' ');
|
||||||
|
users = await User.findAll({
|
||||||
|
include: [{
|
||||||
|
model: FalukantUser,
|
||||||
|
as: 'falukantData',
|
||||||
|
required: true,
|
||||||
|
include: [{
|
||||||
|
model: FalukantCharacter,
|
||||||
|
as: 'character',
|
||||||
|
required: true,
|
||||||
|
include: [{
|
||||||
|
model: FalukantPredefineFirstname,
|
||||||
|
as: 'definedFirstName',
|
||||||
|
required: true,
|
||||||
|
where: {
|
||||||
|
name: firstname
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
model: FalukantPredefineLastname,
|
||||||
|
as: 'definedLastName',
|
||||||
|
required: true,
|
||||||
|
where: {
|
||||||
|
name: lastname
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('no search parameter');
|
||||||
|
}
|
||||||
|
return users.map(user => {
|
||||||
|
return {
|
||||||
|
id: user.hashedId,
|
||||||
|
username: user.username,
|
||||||
|
falukantUser: user.falukantData
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFalukantUserById(userId, hashedId) {
|
||||||
|
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||||
|
throw new Error('noaccess');
|
||||||
|
}
|
||||||
|
const user = await User.findOne({
|
||||||
|
where: {
|
||||||
|
hashedId: hashedId
|
||||||
|
},
|
||||||
|
attributes: ['hashedId', 'username'],
|
||||||
|
include: [{
|
||||||
|
model: FalukantUser,
|
||||||
|
as: 'falukantData',
|
||||||
|
required: true,
|
||||||
|
attributes: ['money', 'certificate', 'id'],
|
||||||
|
include: [{
|
||||||
|
model: FalukantCharacter,
|
||||||
|
as: 'character',
|
||||||
|
attributes: ['birthdate', 'health', 'title_of_nobility'],
|
||||||
|
include: [{
|
||||||
|
model: FalukantPredefineFirstname,
|
||||||
|
as: 'definedFirstName',
|
||||||
|
}, {
|
||||||
|
model: FalukantPredefineLastname,
|
||||||
|
as: 'definedLastName',
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async changeFalukantUser(userId, falukantUserId, falukantData) {
|
||||||
|
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||||
|
throw new Error('noaccess');
|
||||||
|
}
|
||||||
|
const falukantUser = await FalukantUser.findOne({
|
||||||
|
where: {
|
||||||
|
id: falukantUserId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!falukantUser) {
|
||||||
|
throw new Error('notfound');
|
||||||
|
}
|
||||||
|
const character = await FalukantCharacter.findOne({
|
||||||
|
where: {
|
||||||
|
userId: falukantUserId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!character) {
|
||||||
|
throw new Error('notfound');
|
||||||
|
}
|
||||||
|
if (Object.keys(falukantData).indexOf('age') >= 0) {
|
||||||
|
const birthDate = (new Date()) - (falukantData.age * 24 * 3600000);
|
||||||
|
await character.update({
|
||||||
|
birthdate: birthDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Object.keys(falukantData).indexOf('money') >= 0) {
|
||||||
|
await falukantUser.update({
|
||||||
|
money: falukantData.money
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Object.keys(falukantData).indexOf('title_of_nobility') >= 0) {
|
||||||
|
await character.update({
|
||||||
|
titleOfNobility: falukantData.title_of_nobility
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await falukantUser.save();
|
||||||
|
await character.save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new AdminService();
|
export default new AdminService();
|
||||||
@@ -104,7 +104,6 @@ export const loginUser = async ({ username, password }) => {
|
|||||||
user.authCode = authCode;
|
user.authCode = authCode;
|
||||||
await user.save();
|
await user.save();
|
||||||
const friends = await getFriends(user.id);
|
const friends = await getFriends(user.id);
|
||||||
console.log('send login to friends');
|
|
||||||
for (const friend of friends) {
|
for (const friend of friends) {
|
||||||
await notifyUser(friend.hashedId, 'friendloginchanged', {
|
await notifyUser(friend.hashedId, 'friendloginchanged', {
|
||||||
userId: user.hashedId,
|
userId: user.hashedId,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import BaseService from './BaseService.js';
|
import BaseService from './BaseService.js';
|
||||||
import { Sequelize, Op } from 'sequelize';
|
import { Sequelize, Op, where } from 'sequelize';
|
||||||
|
|
||||||
import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js';
|
import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js';
|
||||||
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
|
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
|
||||||
@@ -25,7 +25,14 @@ import BuyableStock from '../models/falukant/data/buyable_stock.js';
|
|||||||
import DirectorProposal from '../models/falukant/data/director_proposal.js';
|
import DirectorProposal from '../models/falukant/data/director_proposal.js';
|
||||||
import Director from '../models/falukant/data/director.js';
|
import Director from '../models/falukant/data/director.js';
|
||||||
import DaySell from '../models/falukant/log/daysell.js';
|
import DaySell from '../models/falukant/log/daysell.js';
|
||||||
|
import MarriageProposal from '../models/falukant/data/marriage_proposal.js';
|
||||||
|
import RelationshipType from '../models/falukant/type/relationship.js';
|
||||||
|
import Relationship from '../models/falukant/data/relationship.js';
|
||||||
|
import PromotionalGift from '../models/falukant/type/promotional_gift.js';
|
||||||
|
import PromotionalGiftCharacterTrait from '../models/falukant/predefine/promotional_gift_character_trait.js';
|
||||||
|
import PromotionalGiftMood from '../models/falukant/predefine/promotional_gift_mood.js';
|
||||||
|
import PromotionalGiftLog from '../models/falukant/log/promotional_gift.js';
|
||||||
|
import CharacterTrait from '../models/falukant/type/character_trait.js';
|
||||||
|
|
||||||
function calcAge(birthdate) {
|
function calcAge(birthdate) {
|
||||||
const b = new Date(birthdate); b.setHours(0, 0);
|
const b = new Date(birthdate); b.setHours(0, 0);
|
||||||
@@ -53,11 +60,33 @@ function calcSellPrice(product, knowledgeFactor = 0) {
|
|||||||
return min + (max - min) * (knowledgeFactor / 100);
|
return min + (max - min) * (knowledgeFactor / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateMarriageCost(titleOfNobility, age) {
|
||||||
|
const minTitle = 1;
|
||||||
|
const adjustedTitle = titleOfNobility - minTitle + 1;
|
||||||
|
const baseCost = 500;
|
||||||
|
return baseCost * Math.pow(adjustedTitle, 1.3) - (age - 12) * 20;
|
||||||
|
}
|
||||||
|
|
||||||
class FalukantService extends BaseService {
|
class FalukantService extends BaseService {
|
||||||
async getFalukantUserByHashedId(hashedId) {
|
async getFalukantUserByHashedId(hashedId) {
|
||||||
return FalukantUser.findOne({
|
const user = await FalukantUser.findOne({
|
||||||
include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }]
|
include: [
|
||||||
|
{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } },
|
||||||
|
{
|
||||||
|
model: FalukantCharacter,
|
||||||
|
as: 'character',
|
||||||
|
include: [
|
||||||
|
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||||
|
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
||||||
|
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
|
||||||
|
{ model: CharacterTrait, as: 'traits', attributes: ['id', 'tr'] }
|
||||||
|
],
|
||||||
|
attributes: ['id', 'birthdate', 'gender']
|
||||||
|
},
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
if (!user) throw new Error('User not found');
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser(hashedUserId) {
|
async getUser(hashedUserId) {
|
||||||
@@ -137,7 +166,7 @@ class FalukantService extends BaseService {
|
|||||||
await FalukantStock.create({ userId: falukantUser.id, regionId: region.id, stockTypeId: stType.id, quantity: 10 });
|
await FalukantStock.create({ userId: falukantUser.id, regionId: region.id, stockTypeId: stType.id, quantity: 10 });
|
||||||
falukantUser.character = ch;
|
falukantUser.character = ch;
|
||||||
const bType = await BranchType.findOne({ where: { labelTr: 'fullstack' } });
|
const bType = await BranchType.findOne({ where: { labelTr: 'fullstack' } });
|
||||||
await Branch.create({ userId: falukantUser.id, regionId: region.id, branchTypeId: bType.id });
|
await Branch.create({ falukantUserId: falukantUser.id, regionId: region.id, branchTypeId: bType.id });
|
||||||
notifyUser(user.hashedId, 'reloadmenu', {});
|
notifyUser(user.hashedId, 'reloadmenu', {});
|
||||||
return falukantUser;
|
return falukantUser;
|
||||||
}
|
}
|
||||||
@@ -209,7 +238,12 @@ class FalukantService extends BaseService {
|
|||||||
const u = await getFalukantUserOrFail(hashedUserId);
|
const u = await getFalukantUserOrFail(hashedUserId);
|
||||||
const b = await getBranchOrFail(u.id, branchId);
|
const b = await getBranchOrFail(u.id, branchId);
|
||||||
const p = await ProductType.findOne({ where: { id: productId } });
|
const p = await ProductType.findOne({ where: { id: productId } });
|
||||||
|
const runningProductions = await Production.findAll({ where: { branchId: b.id } });
|
||||||
|
if (runningProductions.length >= 2) {
|
||||||
|
throw new Error('Too many productions');
|
||||||
|
}
|
||||||
if (!p) throw new Error('Product not found');
|
if (!p) throw new Error('Product not found');
|
||||||
|
quantity = Math.min(100, quantity);
|
||||||
const cost = quantity * p.category * 6;
|
const cost = quantity * p.category * 6;
|
||||||
if (u.money < cost) throw new Error('notenoughmoney');
|
if (u.money < cost) throw new Error('notenoughmoney');
|
||||||
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
|
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
|
||||||
@@ -293,7 +327,7 @@ class FalukantService extends BaseService {
|
|||||||
const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
|
const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
|
||||||
if (!stock) throw new Error('Stock not found');
|
if (!stock) throw new Error('Stock not found');
|
||||||
const inventory = await Inventory.findAll({
|
const inventory = await Inventory.findAll({
|
||||||
where: { stockId: stock.id, quality },
|
where: { quality },
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: ProductType,
|
model: ProductType,
|
||||||
@@ -311,8 +345,12 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
if (!inventory.length) throw new Error('No inventory found');
|
if (!inventory.length) {
|
||||||
|
throw new Error('No inventory found');
|
||||||
|
}
|
||||||
|
console.log(inventory);
|
||||||
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
|
||||||
|
console.log(available);
|
||||||
if (available < quantity) throw new Error('Not enough inventory available');
|
if (available < quantity) throw new Error('Not enough inventory available');
|
||||||
const item = inventory[0].productType;
|
const item = inventory[0].productType;
|
||||||
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
|
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
|
||||||
@@ -330,7 +368,7 @@ class FalukantService extends BaseService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.addSellItem(branchId, falukantUser.id, productId, quantity);
|
await this.addSellItem(branchId, user.id, productId, quantity);
|
||||||
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
|
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
|
||||||
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
|
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
|
||||||
return { success: true };
|
return { success: true };
|
||||||
@@ -384,7 +422,7 @@ class FalukantService extends BaseService {
|
|||||||
for (const item of inventory) {
|
for (const item of inventory) {
|
||||||
const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
|
const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
|
||||||
total += item.quantity * calcSellPrice(item.productType, knowledgeVal);
|
total += item.quantity * calcSellPrice(item.productType, knowledgeVal);
|
||||||
await this.addSellItem(item.stock[0].branch[0].id, falukantUser.id, item.productType.id, item.quantity);
|
await this.addSellItem(item.stock.branch.id, falukantUser.id, item.productType.id, item.quantity);
|
||||||
}
|
}
|
||||||
const moneyResult = await updateFalukantUserMoney(
|
const moneyResult = await updateFalukantUserMoney(
|
||||||
falukantUser.id,
|
falukantUser.id,
|
||||||
@@ -397,7 +435,7 @@ class FalukantService extends BaseService {
|
|||||||
await Inventory.destroy({ where: { id: item.id } });
|
await Inventory.destroy({ where: { id: item.id } });
|
||||||
}
|
}
|
||||||
notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {});
|
notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {});
|
||||||
notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', {});
|
notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', { branchId });
|
||||||
return { success: true, revenue: total };
|
return { success: true, revenue: total };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,7 +446,7 @@ class FalukantService extends BaseService {
|
|||||||
;
|
;
|
||||||
const daySell = await DaySell.findOne({
|
const daySell = await DaySell.findOne({
|
||||||
where: {
|
where: {
|
||||||
regionId: regionId,
|
regionId: branch.regionId,
|
||||||
productId: productId,
|
productId: productId,
|
||||||
sellerId: userId,
|
sellerId: userId,
|
||||||
}
|
}
|
||||||
@@ -418,7 +456,7 @@ class FalukantService extends BaseService {
|
|||||||
await daySell.save();
|
await daySell.save();
|
||||||
} else {
|
} else {
|
||||||
await DaySell.create({
|
await DaySell.create({
|
||||||
regionId: regionId,
|
regionId: branch.regionId,
|
||||||
productId: productId,
|
productId: productId,
|
||||||
sellerId: userId,
|
sellerId: userId,
|
||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
@@ -528,13 +566,22 @@ class FalukantService extends BaseService {
|
|||||||
if (!moneyResult.success) throw new Error('Failed to update money');
|
if (!moneyResult.success) throw new Error('Failed to update money');
|
||||||
buyable.quantity -= amount;
|
buyable.quantity -= amount;
|
||||||
await buyable.save();
|
await buyable.save();
|
||||||
const stock = await FalukantStock.findOne({
|
let stock = await FalukantStock.findOne({
|
||||||
where: { branchId: branch.id, stockTypeId },
|
where: { branchId: branch.id, stockTypeId },
|
||||||
include: [{ model: FalukantStockType, as: 'stockType' }]
|
include: [{ model: FalukantStockType, as: 'stockType' }]
|
||||||
});
|
});
|
||||||
if (!stock) throw new Error('No stock record found for this branch and stockType');
|
if (!stock) {
|
||||||
|
stock = await FalukantStock.create({
|
||||||
|
branchId: branch.id,
|
||||||
|
stockTypeId,
|
||||||
|
quantity: amount,
|
||||||
|
});
|
||||||
|
return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr };
|
||||||
|
}
|
||||||
stock.quantity += amount;
|
stock.quantity += amount;
|
||||||
await stock.save();
|
await stock.save();
|
||||||
|
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
|
||||||
|
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId });
|
||||||
return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr };
|
return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,10 +822,13 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async convertProposalToDirector(hashedUserId, proposalId) {
|
async convertProposalToDirector(hashedUserId, proposalId) {
|
||||||
|
console.log('convert proposal to director - start');
|
||||||
const user = await getFalukantUserOrFail(hashedUserId);
|
const user = await getFalukantUserOrFail(hashedUserId);
|
||||||
|
console.log('convert proposal to director - check user');
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
console.log('convert proposal to director - find proposal', proposalId);
|
||||||
const proposal = await DirectorProposal.findOne(
|
const proposal = await DirectorProposal.findOne(
|
||||||
{
|
{
|
||||||
where: { id: proposalId },
|
where: { id: proposalId },
|
||||||
@@ -787,10 +837,12 @@ class FalukantService extends BaseService {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
console.log('convert proposal to director - check proposal');
|
||||||
if (!proposal || proposal.employerUserId !== user.id) {
|
if (!proposal || proposal.employerUserId !== user.id) {
|
||||||
throw new Error('Proposal does not belong to the user');
|
throw new Error('Proposal does not belong to the user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('convert proposal to director - check existing director', user, proposal);
|
||||||
const existingDirector = await Director.findOne({
|
const existingDirector = await Director.findOne({
|
||||||
where: {
|
where: {
|
||||||
employerUserId: user.id
|
employerUserId: user.id
|
||||||
@@ -808,6 +860,7 @@ class FalukantService extends BaseService {
|
|||||||
if (existingDirector) {
|
if (existingDirector) {
|
||||||
throw new Error('A director already exists for this region');
|
throw new Error('A director already exists for this region');
|
||||||
}
|
}
|
||||||
|
console.log('convert proposal to director - create new director');
|
||||||
const newDirector = await Director.create({
|
const newDirector = await Director.create({
|
||||||
directorCharacterId: proposal.directorCharacterId,
|
directorCharacterId: proposal.directorCharacterId,
|
||||||
employerUserId: proposal.employerUserId,
|
employerUserId: proposal.employerUserId,
|
||||||
@@ -827,11 +880,13 @@ class FalukantService extends BaseService {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
console.log('convert proposal to director - remove propsals');
|
||||||
if (regionUserDirectorProposals.length > 0) {
|
if (regionUserDirectorProposals.length > 0) {
|
||||||
for (const proposal of regionUserDirectorProposals) {
|
for (const proposal of regionUserDirectorProposals) {
|
||||||
await DirectorProposal.destroy();
|
await DirectorProposal.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('convert proposal to director - notify user');
|
||||||
notifyUser(hashedUserId, 'directorchanged');
|
notifyUser(hashedUserId, 'directorchanged');
|
||||||
return newDirector;
|
return newDirector;
|
||||||
}
|
}
|
||||||
@@ -932,59 +987,268 @@ class FalukantService extends BaseService {
|
|||||||
return { result: 'ok' };
|
return { result: 'ok' };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMarriageProposals(hashedUserId) {
|
async getFamily(hashedUserId) {
|
||||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('User not found');
|
||||||
|
}
|
||||||
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||||
if (!character) {
|
if (!character) {
|
||||||
throw new Error('Character not found for this user');
|
throw new Error('Character not found for this user');
|
||||||
}
|
}
|
||||||
const midnight = new Date();
|
const family = {
|
||||||
midnight.setHours(0, 0, 0, 0);
|
relationships: [],
|
||||||
await MarriageProposal.destroy({
|
deathPartners: [],
|
||||||
|
children: [],
|
||||||
|
lovers: [],
|
||||||
|
possiblePartners: [],
|
||||||
|
};
|
||||||
|
let relationships = await Relationship.findAll(
|
||||||
|
{
|
||||||
where: {
|
where: {
|
||||||
[Op.or]: [
|
character1Id: character.id,
|
||||||
{ requesterCharacterId: character.id },
|
|
||||||
{ proposedCharacterId: character.id },
|
|
||||||
],
|
|
||||||
createdAt: {
|
|
||||||
[Op.lt]: midnight,
|
|
||||||
},
|
},
|
||||||
},
|
attributes: ['createdAt', 'widowFirstName2'],
|
||||||
});
|
include: [
|
||||||
let proposals = await MarriageProposal.findAll({
|
{
|
||||||
where: {
|
model: FalukantCharacter,
|
||||||
[Op.or]: [
|
as: 'character2',
|
||||||
{ requesterCharacterId: character.id },
|
attributes: ['id', 'birthdate', 'gender'],
|
||||||
{ proposedCharacterId: character.id },
|
include: [
|
||||||
|
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||||
|
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
if (proposals.length === 0) {
|
model: RelationshipType,
|
||||||
const proposalCount = Math.floor(Math.random() * 4) + 3; // 3–6
|
as: 'relationshipType',
|
||||||
const thirteenDaysAgo = new Date(Date.now() - 13 * 24 * 60 * 60 * 1000);
|
attributes: ['tr'],
|
||||||
const possiblePartners = await FalukantCharacter.findAll({
|
|
||||||
where: {
|
|
||||||
id: { [Op.ne]: character.id },
|
|
||||||
createdAt: { [Op.lt]: thirteenDaysAgo },
|
|
||||||
},
|
},
|
||||||
order: [sequelize.fn('RANDOM')],
|
],
|
||||||
});
|
|
||||||
if (possiblePartners.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
const newProposals = [];
|
);
|
||||||
for (let i = 0; i < proposalCount; i++) {
|
relationships = relationships.map((relationship) => ({
|
||||||
const partner = possiblePartners[i % possiblePartners.length];
|
createdAt: relationship.createdAt,
|
||||||
const createdProposal = await MarriageProposal.create({
|
widowFirstName2: relationship.widowFirstName2,
|
||||||
requesterCharacterId: character.id,
|
character2: {
|
||||||
|
id: relationship.character2.id,
|
||||||
|
age: calcAge(relationship.character2.birthdate),
|
||||||
|
gender: relationship.character2.gender,
|
||||||
|
firstName: relationship.character2.definedFirstName?.name || 'Unknown',
|
||||||
|
nobleTitle: relationship.character2.nobleTitle?.labelTr || '',
|
||||||
|
},
|
||||||
|
relationshipType: relationship.relationshipType.tr,
|
||||||
|
}));
|
||||||
|
family.relationships = relationships.filter((relationship) => ['wooing', 'engaged', 'married'].includes(relationship.relationshipType));
|
||||||
|
family.lovers = relationships.filter((relationship) => ['lover'].includes(relationship.relationshipType.tr));
|
||||||
|
family.deathPartners = relationships.filter((relationship) => ['widowed'].includes(relationship.relationshipType.tr));
|
||||||
|
if (family.relationships.length === 0 ) {
|
||||||
|
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||||
|
if (family.possiblePartners.length === 0) {
|
||||||
|
await this.createPossiblePartners(character.id, character.gender, character.regionId, character.titleOfNobility);
|
||||||
|
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return family;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPossiblePartners(requestingCharacterId) {
|
||||||
|
const proposals = await MarriageProposal.findAll({
|
||||||
|
where: {
|
||||||
|
requesterCharacterId: requestingCharacterId,
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: FalukantCharacter,
|
||||||
|
as: 'proposedCharacter',
|
||||||
|
attributes: ['id', 'firstName', 'lastName', 'gender', 'regionId', 'birthdate'],
|
||||||
|
include: [
|
||||||
|
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||||
|
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
|
||||||
|
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return proposals.map(proposal => {
|
||||||
|
const birthdate = new Date(proposal.proposedCharacter.birthdate);
|
||||||
|
const age = calcAge(birthdate);
|
||||||
|
console.log(proposal.proposedCharacter);
|
||||||
|
return {
|
||||||
|
id: proposal.id,
|
||||||
|
requesterCharacterId: proposal.requesterCharacterId,
|
||||||
|
proposedCharacterId: proposal.proposedCharacter.id,
|
||||||
|
proposedCharacterName: `${proposal.proposedCharacter.definedFirstName?.name} ${proposal.proposedCharacter.definedLastName?.name}`,
|
||||||
|
proposedCharacterGender: proposal.proposedCharacter.gender,
|
||||||
|
proposedCharacterRegionId: proposal.proposedCharacter.regionId,
|
||||||
|
proposedCharacterAge: age,
|
||||||
|
proposedCharacterNobleTitle: proposal.proposedCharacter.nobleTitle.labelTr,
|
||||||
|
cost: proposal.cost,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility) {
|
||||||
|
try {
|
||||||
|
const minTitleResult = await TitleOfNobility.findOne({
|
||||||
|
order: [['id', 'ASC']],
|
||||||
|
attributes: ['id'],
|
||||||
|
});
|
||||||
|
if (!minTitleResult) {
|
||||||
|
throw new Error('No title of nobility found');
|
||||||
|
}
|
||||||
|
const minTitle = minTitleResult.id;
|
||||||
|
const potentialPartners = await FalukantCharacter.findAll({
|
||||||
|
where: {
|
||||||
|
id: { [Op.ne]: requestingCharacterId },
|
||||||
|
gender: { [Op.ne]: requestingCharacterGender },
|
||||||
|
regionId: requestingRegionId,
|
||||||
|
createdAt: { [Op.lt]: new Date(new Date() - 12 * 24 * 60 * 60 * 1000) },
|
||||||
|
titleOfNobility: { [Op.between]: [requestingCharacterTitleOfNobility - 1, requestingCharacterTitleOfNobility + 1] }
|
||||||
|
},
|
||||||
|
limit: 5,
|
||||||
|
});
|
||||||
|
const proposals = potentialPartners.map(partner => {
|
||||||
|
const age = calcAge(partner.birthdate);
|
||||||
|
return {
|
||||||
|
requesterCharacterId: requestingCharacterId,
|
||||||
proposedCharacterId: partner.id,
|
proposedCharacterId: partner.id,
|
||||||
courtingProgress: 0,
|
cost: calculateMarriageCost(partner.titleOfNobility, age, minTitle),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
newProposals.push(createdProposal);
|
await MarriageProposal.bulkCreate(proposals);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating possible partners:', error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
proposals = newProposals;
|
|
||||||
}
|
}
|
||||||
return proposals;
|
|
||||||
|
async acceptMarriageProposal(hashedUserId, proposedCharacterId) {
|
||||||
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
|
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('User not found');
|
||||||
|
}
|
||||||
|
const proposal = await MarriageProposal.findOne({
|
||||||
|
where: {
|
||||||
|
requesterCharacterId: character.id,
|
||||||
|
proposedCharacterId: proposedCharacterId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!proposal) {
|
||||||
|
throw new Error('Proposal not found');
|
||||||
|
}
|
||||||
|
if (user.money < proposal.cost) {
|
||||||
|
console.log(user, proposal);
|
||||||
|
throw new Error('Not enough money to accept the proposal');
|
||||||
|
}
|
||||||
|
const moneyResult = await updateFalukantUserMoney(user.id, -proposal.cost, 'Marriage cost', user.id);
|
||||||
|
if (!moneyResult.success) {
|
||||||
|
throw new Error('Failed to update money');
|
||||||
|
}
|
||||||
|
const marriedType = await RelationshipType.findOne({
|
||||||
|
where: { tr: 'wooing' },
|
||||||
|
});
|
||||||
|
if (!marriedType) {
|
||||||
|
throw new Error('Relationship type "married" not found');
|
||||||
|
}
|
||||||
|
await Relationship.create({
|
||||||
|
character1Id: proposal.requesterCharacterId,
|
||||||
|
character2Id: proposal.proposedCharacterId,
|
||||||
|
relationshipTypeId: marriedType.id,
|
||||||
|
});
|
||||||
|
await MarriageProposal.destroy({
|
||||||
|
where: { character1Id: character.id },
|
||||||
|
})
|
||||||
|
;
|
||||||
|
return { success: true, message: 'Marriage proposal accepted' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGifts(hashedUserId) {
|
||||||
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
|
const character = await FalukantCharacter.findOne({
|
||||||
|
where: { userId: user.id },
|
||||||
|
});
|
||||||
|
if (!character) {
|
||||||
|
throw new Error('Character not found');
|
||||||
|
}
|
||||||
|
let gifts = await PromotionalGift.findAll();
|
||||||
|
const lowestTitleOfNobility = await TitleOfNobility.findOne({
|
||||||
|
order: [['id', 'ASC']],
|
||||||
|
});
|
||||||
|
return await Promise.all(gifts.map(async (gift) => {
|
||||||
|
return {
|
||||||
|
id: gift.id,
|
||||||
|
name: gift.name,
|
||||||
|
cost: await this.getGiftCost(gift.value, character.titleOfNobility, lowestTitleOfNobility.id),
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendGift(hashedUserId, giftId) {
|
||||||
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
|
const lowestTitleOfNobility = await TitleOfNobility.findOne({
|
||||||
|
order: [['id', 'ASC']],
|
||||||
|
});
|
||||||
|
const relation = Relationship.findOne({
|
||||||
|
where: {
|
||||||
|
character1Id: user.character.id,
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: RelationshipType,
|
||||||
|
as: 'relationshipType',
|
||||||
|
where: { tr: 'wooing' },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!relation) {
|
||||||
|
throw new Error('User and character are not related');
|
||||||
|
}
|
||||||
|
console.log(user);
|
||||||
|
const gift = await PromotionalGift.findOne({
|
||||||
|
where: { id: giftId },
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: PromotionalGiftCharacterTrait,
|
||||||
|
as: 'characterTraits',
|
||||||
|
where: { trait_id: { [Op.in]: user.character.characterTraits.map(trait => trait.id) }, },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: PromotionalGiftMood,
|
||||||
|
as: 'promotionalgiftmoods',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const cost = await this.getGiftCost(gift.value, user.character.titleOfNobility, lowestTitleOfNobility.id);
|
||||||
|
if (user.money < cost) {
|
||||||
|
console.log(user, user.money, cost);
|
||||||
|
throw new Error('Not enough money to send the gift');
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify(gift));
|
||||||
|
const changeValue = gift.characterTraits.suitability + gift.promotionalgiftmoods.suitability - 4;
|
||||||
|
this.updateFalukantUserMoney(user.id, -cost, 'Gift cost', user.id);
|
||||||
|
await relation.update({ value: relation.value + changeValue });
|
||||||
|
await PromotionalGiftLog.create({
|
||||||
|
senderCharacterId: user.character.id,
|
||||||
|
recipientCharacterId: relation.character2Id,
|
||||||
|
giftId: giftId,
|
||||||
|
changeValue: changeValue,
|
||||||
|
});
|
||||||
|
return { success: true, message: 'Gift sent' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGiftCost(value, titleOfNobility, lowestTitleOfNobility) {
|
||||||
|
const titleLevel = titleOfNobility - lowestTitleOfNobility + 1;
|
||||||
|
return Math.round(value * Math.pow(1 + titleLevel * 0.3, 1.3) * 100) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTitlesOfNobility() {
|
||||||
|
return TitleOfNobility.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHouseTypes() {
|
||||||
|
// return House
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,16 +231,16 @@ async function initializeFalukantProducts() {
|
|||||||
await ProductType.bulkCreate([
|
await ProductType.bulkCreate([
|
||||||
{ labelTr: 'wheat', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'wheat', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'grain', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'grain', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 4 },
|
{ labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 46},
|
||||||
{ labelTr: 'fish', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'fish', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'meat', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'meat', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'leather', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'leather', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'wood', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'wood', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'stone', category: 1, productionTime: 2, sellCost: 7 },
|
{ labelTr: 'stone', category: 1, productionTime: 2, sellCost: 7 },
|
||||||
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 4 },
|
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 6 },
|
||||||
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 4 },
|
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 6 },
|
||||||
{ labelTr: 'bread', category: 1, productionTime: 1, sellCost: 4 },
|
{ labelTr: 'bread', category: 1, productionTime: 1, sellCost: 6 },
|
||||||
{ labelTr: 'beer', category: 2, productionTime: 3, sellCost: 4 },
|
{ labelTr: 'beer', category: 2, productionTime: 3, sellCost: 6 },
|
||||||
{ 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 },
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ import PromotionalGift from "../../models/falukant/type/promotional_gift.js";
|
|||||||
import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promotional_gift_character_trait.js";
|
import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promotional_gift_character_trait.js";
|
||||||
import PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js";
|
import PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js";
|
||||||
|
|
||||||
|
export const initializeFalukantTypes = async () => {
|
||||||
|
await initializeFalukantTypeRegions();
|
||||||
|
await initializeFalukantRelationships();
|
||||||
|
await initializeFalukantMoods();
|
||||||
|
await initializeFalukantCharacterTraits();
|
||||||
|
await initializeFalukantPromotionalGifts();
|
||||||
|
await initializePromotionalGiftMoodLinks();
|
||||||
|
};
|
||||||
|
|
||||||
const regionTypes = [];
|
const regionTypes = [];
|
||||||
const regionTypeTrs = [
|
const regionTypeTrs = [
|
||||||
"country",
|
"country",
|
||||||
@@ -33,7 +42,8 @@ const relationships = [
|
|||||||
{ tr: "wooing" },
|
{ tr: "wooing" },
|
||||||
{ tr: "engaged" },
|
{ tr: "engaged" },
|
||||||
{ tr: "married" },
|
{ tr: "married" },
|
||||||
{ tr: "widowed" }
|
{ tr: "widowed" },
|
||||||
|
{ tr: "lover" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const moods = [
|
const moods = [
|
||||||
@@ -194,15 +204,6 @@ const promotionalGiftMoodLinks = [
|
|||||||
{ gift: "Horse", mood: "nervous", suitability: 4 },
|
{ gift: "Horse", mood: "nervous", suitability: 4 },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const initializeFalukantTypes = async () => {
|
|
||||||
await initializeFalukantTypeRegions();
|
|
||||||
await initializeFalukantRelationships();
|
|
||||||
await initializeFalukantMoods();
|
|
||||||
await initializeFalukantCharacterTraits();
|
|
||||||
await initializeFalukantPromotionalGifts();
|
|
||||||
await initializePromotionalGiftMoodLinks();
|
|
||||||
};
|
|
||||||
|
|
||||||
const initializeFalukantTypeRegions = async () => {
|
const initializeFalukantTypeRegions = async () => {
|
||||||
for (const regionType of regionTypeTrs) {
|
for (const regionType of regionTypeTrs) {
|
||||||
const [regionTypeRecord] = await RegionType.findOrCreate({
|
const [regionTypeRecord] = await RegionType.findOrCreate({
|
||||||
@@ -326,3 +327,4 @@ export const initializePromotionalGiftMoodLinks = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ const syncDatabase = async () => {
|
|||||||
console.log("Setting up associations...");
|
console.log("Setting up associations...");
|
||||||
setupAssociations();
|
setupAssociations();
|
||||||
|
|
||||||
console.log("Creating triggers...");
|
|
||||||
await createTriggers();
|
|
||||||
|
|
||||||
console.log("Initializing settings...");
|
console.log("Initializing settings...");
|
||||||
await initializeSettings();
|
await initializeSettings();
|
||||||
|
|
||||||
@@ -43,6 +40,9 @@ const syncDatabase = async () => {
|
|||||||
console.log("Initializing Falukant...");
|
console.log("Initializing Falukant...");
|
||||||
await initializeFalukant();
|
await initializeFalukant();
|
||||||
|
|
||||||
|
console.log("Creating triggers...");
|
||||||
|
await createTriggers();
|
||||||
|
|
||||||
console.log('Database synchronization complete.');
|
console.log('Database synchronization complete.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Unable to synchronize the database:', error);
|
console.error('Unable to synchronize the database:', error);
|
||||||
|
|||||||
BIN
frontend/public/images/falukant/avatar/female00.jpg
Normal file
|
After Width: | Height: | Size: 549 KiB |
BIN
frontend/public/images/falukant/avatar/female01.jpg
Normal file
|
After Width: | Height: | Size: 576 KiB |
BIN
frontend/public/images/falukant/avatar/female02.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
frontend/public/images/falukant/avatar/female03.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
frontend/public/images/falukant/avatar/male00.jpg
Normal file
|
After Width: | Height: | Size: 610 KiB |
BIN
frontend/public/images/falukant/avatar/male01.jpg
Normal file
|
After Width: | Height: | Size: 618 KiB |
BIN
frontend/public/images/falukant/avatar/male02.jpg
Normal file
|
After Width: | Height: | Size: 641 KiB |
BIN
frontend/public/images/falukant/avatar/male03.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
frontend/public/images/icons/falukant/bank.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
frontend/public/images/icons/falukant/darknet.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
frontend/public/images/icons/falukant/directors.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
frontend/public/images/icons/falukant/education.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
frontend/public/images/icons/falukant/family.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
frontend/public/images/icons/falukant/health.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
frontend/public/images/icons/falukant/house.jpg
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
frontend/public/images/icons/falukant/moneyhistory.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
frontend/public/images/icons/falukant/nobility.jpg
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
frontend/public/images/icons/falukant/overview.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
frontend/public/images/icons/falukant/politics.jpg
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
frontend/public/images/icons/falukant/product-carrot.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
frontend/public/images/icons/falukant/product-fish.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
frontend/public/images/icons/falukant/product-grain.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
frontend/public/images/icons/falukant/product-leather.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
frontend/public/images/icons/falukant/product-meat.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
frontend/public/images/icons/falukant/product-wheat.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
frontend/public/images/icons/falukant/reputation.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
frontend/public/images/icons/falukant/storage-field.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/public/images/icons/falukant/storage-iron.png
Normal file
|
After Width: | Height: | Size: 944 KiB |
BIN
frontend/public/images/icons/falukant/storage-stone.png
Normal file
|
After Width: | Height: | Size: 952 KiB |
BIN
frontend/public/images/icons/falukant/storage-wood.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
frontend/public/images/icons/falukant/towns.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="logo"><img src="/images/icons/logo_color.png"></div>
|
<div class="logo" @click="showFalukantDaemonStatus"><img src="/images/icons/logo_color.png"></div>
|
||||||
<div class="window-bar">
|
<div class="window-bar">
|
||||||
<button v-for="dialog in openDialogs" :key="dialog.dialog.name" class="dialog-button"
|
<button v-for="dialog in openDialogs" :key="dialog.dialog.name" class="dialog-button"
|
||||||
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.localTitle">
|
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.localTitle">
|
||||||
@@ -18,14 +18,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapState } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppFooter',
|
name: 'AppFooter',
|
||||||
components: {
|
components: {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('dialogs', ['openDialogs'])
|
...mapGetters('dialogs', ['openDialogs']),
|
||||||
|
...mapState(['daemonSocket']),
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.daemonSocket.addEventListener('workerStatus', () => { console.log('----'); });
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
this.daemonSocket.removeEventListener('workerStatus', this.handleDaemonMessage);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openImprintDialog() {
|
openImprintDialog() {
|
||||||
@@ -39,6 +46,13 @@ export default {
|
|||||||
},
|
},
|
||||||
toggleDialogMinimize(dialogName) {
|
toggleDialogMinimize(dialogName) {
|
||||||
this.$store.dispatch('dialogs/toggleDialogMinimize', dialogName);
|
this.$store.dispatch('dialogs/toggleDialogMinimize', dialogName);
|
||||||
|
},
|
||||||
|
async showFalukantDaemonStatus() {
|
||||||
|
this.daemonSocket.send('{"event": "getWorkerStatus"}');
|
||||||
|
},
|
||||||
|
handleDaemonMessage(event) {
|
||||||
|
const status = JSON.parse(event.data);
|
||||||
|
console.log(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,11 +13,20 @@
|
|||||||
class="submenu-icon"> </span>
|
class="submenu-icon"> </span>
|
||||||
<span>{{ $t(`navigation.m-${key}.${subkey}`) }}</span>
|
<span>{{ $t(`navigation.m-${key}.${subkey}`) }}</span>
|
||||||
<span v-if="subkey === 'forum'" class="subsubmenu">▶</span>
|
<span v-if="subkey === 'forum'" class="subsubmenu">▶</span>
|
||||||
|
<span v-else-if="subitem.children" class="subsubmenu">▶</span>
|
||||||
<ul v-if="subkey === 'forum' && forumList.length > 0" class="submenu2">
|
<ul v-if="subkey === 'forum' && forumList.length > 0" class="submenu2">
|
||||||
<li v-for="forum in forumList" :key="forum.id" @click="openForum(forum.id, $event)">
|
<li v-for="forum in forumList" :key="forum.id" @click="openForum(forum.id, $event)">
|
||||||
{{ forum.name }}
|
{{ forum.name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ul v-else-if="subitem.children" class="submenu2">
|
||||||
|
<li v-for="(subsubitem, subsubkey) in subitem.children" :key="subsubitem.text"
|
||||||
|
@click="openPage(subsubitem.path ?? null)">
|
||||||
|
<span v-if="subsubitem.icon" :style="`background-image:url('/images/icons/${subsubitem.icon}')`"
|
||||||
|
class="submenu-icon"> </span>
|
||||||
|
<span>{{ $t(`navigation.m-${key}.m-${subkey}.${subsubkey}`) }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="item.showLoggedinFriends === 1 && friendsList.length > 0" v-for="friend in friendsList" :key="friend.id">
|
<li v-if="item.showLoggedinFriends === 1 && friendsList.length > 0" v-for="friend in friendsList" :key="friend.id">
|
||||||
{{ friend.username }}
|
{{ friend.username }}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
visible(newValue) {
|
visible(newValue) {
|
||||||
if (!newValue) {
|
if (!newValue) {
|
||||||
this.minimized = false; // Reset minimized state when dialog is closed
|
this.minimized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
64
frontend/src/components/falukant/BranchSelection.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div class="branch-selection">
|
||||||
|
<h3>{{ $t('falukant.branch.selection.title') }}</h3>
|
||||||
|
<div>
|
||||||
|
<FormattedDropdown
|
||||||
|
:options="branches"
|
||||||
|
:columns="branchColumns"
|
||||||
|
v-model="localSelectedBranch"
|
||||||
|
:placeholder="$t('falukant.branch.selection.placeholder')"
|
||||||
|
@input="updateSelectedBranch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button @click="$emit('createBranch')">{{ $t('falukant.branch.actions.create') }}</button>
|
||||||
|
<button @click="$emit('upgradeBranch')" :disabled="!localSelectedBranch">
|
||||||
|
{{ $t('falukant.branch.actions.upgrade') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FormattedDropdown from '@/components/form/FormattedDropdown.vue';
|
||||||
|
export default {
|
||||||
|
name: "BranchSelection",
|
||||||
|
components: { FormattedDropdown },
|
||||||
|
props: {
|
||||||
|
branches: { type: Array, required: true },
|
||||||
|
selectedBranch: { type: Object, default: null },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localSelectedBranch: this.selectedBranch,
|
||||||
|
branchColumns: [
|
||||||
|
{ field: "cityName", label: this.$t('falukant.branch.columns.city') },
|
||||||
|
{ field: "type", label: this.$t('falukant.branch.columns.type') },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedBranch(newVal) {
|
||||||
|
this.localSelectedBranch = newVal;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateSelectedBranch(value) {
|
||||||
|
this.$emit('branchSelected', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.branch-selection {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
120
frontend/src/components/falukant/DirectorInfo.vue
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div class="director-info">
|
||||||
|
<h3>{{ $t('falukant.branch.director.title') }}</h3>
|
||||||
|
<div v-if="!director || director === null">
|
||||||
|
<button @click="openNewDirectorDialog">{{ $t('falukant.branch.director.actions.new') }}</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="director-info-container">
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.branch.director.name') }}</td>
|
||||||
|
<td>
|
||||||
|
{{ $t('falukant.titles.' + director.character.gender + '.' + director.character.title) }}
|
||||||
|
{{ director.character.name }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.branch.director.salary') }}</td>
|
||||||
|
<td>{{ director.income }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.branch.director.satisfaction') }}</td>
|
||||||
|
<td>{{ director.satisfaction }} %</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><button @click="fireDirector">{{ $t('falukant.branch.director.fire') }}</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><button @click="teachDirector">{{ $t('falukant.branch.director.teach') }}</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" v-model="director.mayProduce" @change="saveSetting('mayProduce', director.mayProduce)">
|
||||||
|
{{ $t('falukant.branch.director.produce') }}
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Ähnliche Checkboxen für maySell und mayStartTransport -->
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NewDirectorDialog ref="newDirectorDialog" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
import NewDirectorDialog from '@/dialogues/falukant/NewDirectorDialog.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DirectorInfo",
|
||||||
|
props: { branchId: { type: Number, required: true } },
|
||||||
|
components: {
|
||||||
|
NewDirectorDialog
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
director: null,
|
||||||
|
showNewDirectorDialog: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadDirector();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadDirector() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/api/falukant/director/${this.branchId}`);
|
||||||
|
this.director = Object.keys(response.data).length === 0 || !response.data.director ? null : response.data.director;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading director:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async saveSetting(settingKey, value) {
|
||||||
|
if (!this.director) return;
|
||||||
|
try {
|
||||||
|
await apiClient.post(`/api/falukant/director/settings`, {
|
||||||
|
branchId: this.branchId,
|
||||||
|
directorId: this.director.id,
|
||||||
|
settingKey,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error saving setting ${settingKey}:`, error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openNewDirectorDialog() {
|
||||||
|
console.log('openNewDirectorDialog');
|
||||||
|
this.$refs.newDirectorDialog.open(this.branchId);
|
||||||
|
},
|
||||||
|
fireDirector() {
|
||||||
|
alert(this.$t('falukant.branch.director.fireAlert'));
|
||||||
|
},
|
||||||
|
teachDirector() {
|
||||||
|
alert(this.$t('falukant.branch.director.teachAlert'));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.director-info {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.director-info-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.director-info-container > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
182
frontend/src/components/falukant/ProductionSection.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div class="production-section">
|
||||||
|
<h3>{{ $t('falukant.branch.production.title') }}</h3>
|
||||||
|
<div v-if="productions && 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) }} s</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div v-if="(!productions || 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" max="200"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>{{ $t('falukant.branch.production.cost') }}:
|
||||||
|
<strong>{{ calculateProductionCost() }}</strong>
|
||||||
|
</p>
|
||||||
|
<p>{{ $t('falukant.branch.production.duration') }}:
|
||||||
|
<strong>{{ calculateProductionDuration(selectedProduct) }}</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
export default {
|
||||||
|
name: "ProductionSection",
|
||||||
|
props: {
|
||||||
|
branchId: { type: Number, required: true },
|
||||||
|
products: { type: Array, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
productions: [],
|
||||||
|
selectedProduct: null,
|
||||||
|
productionQuantity: 1,
|
||||||
|
currentTime: Date.now(),
|
||||||
|
timer: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadProductions();
|
||||||
|
this.timer = setInterval(() => {
|
||||||
|
this.currentTime = Date.now();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadProductions() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/api/falukant/branches/${this.branchId}`);
|
||||||
|
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 loading productions:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateProductionCost() {
|
||||||
|
if (!this.products) return 0;
|
||||||
|
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||||
|
return product ? 6 * product.category * this.productionQuantity : 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;
|
||||||
|
return (totalMinutes / 60).toFixed(2).replace('.', ':');
|
||||||
|
},
|
||||||
|
calculateProductionRevenue() {
|
||||||
|
if (!this.selectedProduct || !this.products) return 0;
|
||||||
|
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||||
|
if (!product) return 0;
|
||||||
|
const revenue = this.calculateProductRevenue(product).absolute * this.productionQuantity;
|
||||||
|
return revenue.toFixed(2).toLocaleString();
|
||||||
|
},
|
||||||
|
calculateEndDateTime(startTimestamp, productionTime) {
|
||||||
|
const start = new Date(startTimestamp);
|
||||||
|
const end = new Date(start.getTime() + productionTime * 60 * 1000);
|
||||||
|
return end.toLocaleString();
|
||||||
|
},
|
||||||
|
calculateRemainingTime(startTimestamp, productionTime) {
|
||||||
|
const start = new Date(startTimestamp).getTime();
|
||||||
|
const end = start + productionTime * 60 * 1000;
|
||||||
|
const secondsLeft = Math.max(Math.floor((end - this.currentTime) / 1000), 0);
|
||||||
|
return secondsLeft;
|
||||||
|
},
|
||||||
|
async startProduction() {
|
||||||
|
if (this.selectedProduct && this.productionQuantity > 0) {
|
||||||
|
this.productionQuantity = Math.min(this.productionQuantity, 200);
|
||||||
|
let productionQuantity = this.productionQuantity;
|
||||||
|
while (productionQuantity > 0) {
|
||||||
|
const sendQuantitiy = Math.min(productionQuantity, 100);
|
||||||
|
productionQuantity -= sendQuantitiy;
|
||||||
|
try {
|
||||||
|
await apiClient.post(`/api/falukant/production`, {
|
||||||
|
branchId: this.branchId,
|
||||||
|
productId: this.selectedProduct,
|
||||||
|
quantity: sendQuantitiy,
|
||||||
|
});
|
||||||
|
this.loadProductions();
|
||||||
|
} catch (error) {
|
||||||
|
alert(this.$t(`falukant.branch.production.error${error.response.data.error}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
calculateProductRevenue(product) {
|
||||||
|
if (!product.knowledges || product.knowledges.length === 0) {
|
||||||
|
return { absolute: 0, perMinute: 0 };
|
||||||
|
}
|
||||||
|
const knowledgeFactor = product.knowledges[0].knowledge || 0;
|
||||||
|
const maxPrice = product.sellCost;
|
||||||
|
const minPrice = maxPrice * 0.6;
|
||||||
|
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||||
|
const perMinute = product.productionTime > 0 ? revenuePerUnit / product.productionTime : 0;
|
||||||
|
return {
|
||||||
|
absolute: revenuePerUnit.toFixed(2),
|
||||||
|
perMinute: perMinute.toFixed(2),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.production-section {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
98
frontend/src/components/falukant/RevenueSection.vue
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<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>
|
||||||
|
<th>{{ $t('falukant.branch.revenue.profitAbsolute') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.revenue.profitPerMinute') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="product in products" :key="product.id" :class="{ highlight: product.id === productWithMaxRevenuePerMinute?.id }">
|
||||||
|
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||||
|
<td>{{ product.knowledges && product.knowledges[0] ? product.knowledges[0].knowledge : 0 }}</td>
|
||||||
|
<td>{{ calculateProductRevenue(product).absolute }}</td>
|
||||||
|
<td>{{ calculateProductRevenue(product).perMinute }}</td>
|
||||||
|
<td>{{ calculateProductProfit(product).absolute }}</td>
|
||||||
|
<td>{{ calculateProductProfit(product).perMinute }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "RevenueSection",
|
||||||
|
props: {
|
||||||
|
products: { type: Array, required: true },
|
||||||
|
calculateProductRevenue: { type: Function, required: true },
|
||||||
|
calculateProductProfit: { type: Function, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isRevenueTableOpen: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
productWithMaxRevenuePerMinute() {
|
||||||
|
if (!this.products || this.products.length === 0) return null;
|
||||||
|
return this.products.reduce((maxProduct, currentProduct) => {
|
||||||
|
const currentRevenue = parseFloat(this.calculateProductRevenue(currentProduct).perMinute);
|
||||||
|
const maxRevenue = maxProduct ? parseFloat(this.calculateProductRevenue(maxProduct).perMinute) : 0;
|
||||||
|
return currentRevenue > maxRevenue ? currentProduct : maxProduct;
|
||||||
|
}, null);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleRevenueTable() {
|
||||||
|
this.isRevenueTableOpen = !this.isRevenueTableOpen;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.revenue-section {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.revenue-section button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #007bff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1em;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.revenue-table {
|
||||||
|
margin-top: 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.revenue-table table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
background-color: #dfffd6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
101
frontend/src/components/falukant/SaleSection.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sale-section">
|
||||||
|
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
|
||||||
|
<!-- Beispielhafte Inventar-Tabelle -->
|
||||||
|
<div v-if="inventory.length > 0" class="inventory-table">
|
||||||
|
<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 }}</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" />
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
export default {
|
||||||
|
name: "SaleSection",
|
||||||
|
props: { branchId: { type: Number, required: true } },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inventory: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadInventory();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadInventory() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/api/falukant/inventory/${this.branchId}`);
|
||||||
|
this.inventory = response.data.map(item => ({
|
||||||
|
...item,
|
||||||
|
sellQuantity: item.totalQuantity,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading inventory:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sellItem(index) {
|
||||||
|
const item = this.inventory[index];
|
||||||
|
const quantityToSell = item.sellQuantity || item.totalQuantity;
|
||||||
|
apiClient.post(`/api/falukant/sell`, {
|
||||||
|
branchId: this.branchId,
|
||||||
|
productId: item.product.id,
|
||||||
|
quantity: quantityToSell,
|
||||||
|
quality: item.quality,
|
||||||
|
}).catch(() => {
|
||||||
|
alert(this.$t('falukant.branch.sale.sellError'));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sellAll() {
|
||||||
|
apiClient.post(`/api/falukant/sell/all`, { branchId: this.branchId })
|
||||||
|
.catch(() => {
|
||||||
|
alert(this.$t('falukant.branch.sale.sellAllError'));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sale-section {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.inventory-table table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.inventory-table th,
|
||||||
|
.inventory-table td {
|
||||||
|
padding: 2px 3px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -5,11 +5,16 @@
|
|||||||
<span class="status-icon">{{ item.icon }}: {{ item.value }}</span>
|
<span class="status-icon">{{ item.icon }}: {{ item.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<span v-if="statusItems.length > 0">
|
||||||
|
<template v-for="(menuItem, key) in menu.falukant.children" :key="menuItem.id" >
|
||||||
|
<img :src="'/images/icons/falukant/' + key + '.jpg'" class="menu-icon" @click="openPage(menuItem)" :title="$t(`navigation.m-falukant.${key}`)" />
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapState, mapGetters } from "vuex";
|
||||||
import apiClient from "@/utils/axios.js";
|
import apiClient from "@/utils/axios.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -25,18 +30,25 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["socket"]),
|
...mapState(["socket", "daemonSocket"]),
|
||||||
|
...mapGetters(['menu']),
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.fetchStatus();
|
await this.fetchStatus();
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on("falukantUpdateStatus", this.fetchStatus);
|
this.socket.on("falukantUpdateStatus", this.fetchStatus);
|
||||||
}
|
}
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.addEventListener("message", this.handleDaemonSocketMessage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.off("falukantUpdateStatus", this.fetchStatus);
|
this.socket.off("falukantUpdateStatus", this.fetchStatus);
|
||||||
}
|
}
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.removeEventListener("message", this.handleDaemonSocketMessage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchStatus() {
|
async fetchStatus() {
|
||||||
@@ -66,6 +78,24 @@ export default {
|
|||||||
console.error("Error fetching status:", error);
|
console.error("Error fetching status:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async handleDaemonSocketMessage(event) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.event === "falukantUpdateStatus") {
|
||||||
|
this.fetchStatus();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing daemonSocket message:", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openPage(url, hasSubmenu = false) {
|
||||||
|
if (hasSubmenu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (url) {
|
||||||
|
this.$router.push(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -92,4 +122,11 @@ export default {
|
|||||||
.status-icon {
|
.status-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 2px 0 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
220
frontend/src/components/falukant/StorageSection.vue
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
<template>
|
||||||
|
<div class="storage-section">
|
||||||
|
<h3>{{ $t('falukant.branch.storage.title') }}</h3>
|
||||||
|
<div class="storage-info">
|
||||||
|
<p>
|
||||||
|
{{ $t('falukant.branch.storage.currentCapacity') }}:
|
||||||
|
<strong>{{ currentStorage }} / {{ maxStorage }}</strong>
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.branch.storage.stockType') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.storage.totalCapacity') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.storage.used') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(usage, idx) in storageUsage" :key="idx">
|
||||||
|
<td>{{ $t(`falukant.branch.stocktype.${usage.stockTypeLabelTr}`) }}</td>
|
||||||
|
<td>{{ usage.totalCapacity }}</td>
|
||||||
|
<td>{{ usage.used }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h4>{{ $t('falukant.branch.storage.availableToBuy') }}</h4>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.branch.storage.stockType') }}</th>
|
||||||
|
<th>{{ $t('falukant.branch.storage.totalCapacity') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Hier zeigen wir die gruppierten (nach Label) Einträge an -->
|
||||||
|
<tr v-for="(group, i) in buyableUsage" :key="i">
|
||||||
|
<td>{{ $t(`falukant.branch.stocktype.${group.stockTypeLabelTr}`) }}</td>
|
||||||
|
<td>{{ group.totalQuantity }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="storage-market">
|
||||||
|
<div class="buy-section">
|
||||||
|
<label>{{ $t('falukant.branch.storage.selectStockType') }}</label>
|
||||||
|
<!-- Auswahl basiert jetzt auf dem Label -->
|
||||||
|
<select v-model="selectedBuyStockTypeLabelTr">
|
||||||
|
<option v-for="group in buyableUsage" :key="group.stockTypeLabelTr" :value="group.stockTypeLabelTr">
|
||||||
|
{{ $t(`falukant.branch.stocktype.${group.stockTypeLabelTr}`) }} - {{ getCostOfType(group.stockTypeLabelTr) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div>
|
||||||
|
<label>{{ $t('falukant.branch.storage.buyAmount') }}</label>
|
||||||
|
<input type="number" v-model.number="buyStorageAmount" :max="maxBuyableForSelectedBuy" min="1" />
|
||||||
|
<button @click="onBuyStorage">
|
||||||
|
{{ $t('falukant.branch.storage.buyStorageButton') }} ({{ buyCost }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sell-section">
|
||||||
|
<label>{{ $t('falukant.branch.storage.selectStockType') }}</label>
|
||||||
|
<select v-model="selectedSellStockTypeId">
|
||||||
|
<option v-for="type in storageUsage" :key="type.stockTypeId" :value="type.stockTypeId">
|
||||||
|
{{ $t(`falukant.branch.stocktype.${type.stockTypeLabelTr}`) }} - {{ getCostOfTypeById(type.stockTypeId) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div>
|
||||||
|
<label>{{ $t('falukant.branch.storage.sellAmount') }}</label>
|
||||||
|
<input type="number" v-model.number="sellStorageAmount" :max="maxSellableForSelectedSell" min="1" />
|
||||||
|
<button @click="onSellStorage">
|
||||||
|
{{ $t('falukant.branch.storage.sellStorageButton') }} ({{ sellIncome }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
export default {
|
||||||
|
name: "StorageSection",
|
||||||
|
props: { branchId: { type: Number, required: true } },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentStorage: 0,
|
||||||
|
maxStorage: 0,
|
||||||
|
storageUsage: [],
|
||||||
|
buyableUsage: [],
|
||||||
|
buyStorageAmount: 0,
|
||||||
|
sellStorageAmount: 0,
|
||||||
|
stockTypes: [],
|
||||||
|
selectedBuyStockTypeLabelTr: null,
|
||||||
|
selectedSellStockTypeId: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
buyCost() {
|
||||||
|
const cost = this.getCostOfType(this.selectedBuyStockTypeLabelTr);
|
||||||
|
return this.buyStorageAmount * cost;
|
||||||
|
},
|
||||||
|
sellIncome() {
|
||||||
|
const cost = this.getCostOfTypeById(this.selectedSellStockTypeId);
|
||||||
|
return this.sellStorageAmount * cost;
|
||||||
|
},
|
||||||
|
maxBuyableForSelectedBuy() {
|
||||||
|
const group = this.buyableUsage.find(g => g.stockTypeLabelTr === this.selectedBuyStockTypeLabelTr);
|
||||||
|
return group ? group.totalQuantity : 0;
|
||||||
|
},
|
||||||
|
maxSellableForSelectedSell() {
|
||||||
|
const usage = this.storageUsage.find(u => u.stockTypeId === this.selectedSellStockTypeId);
|
||||||
|
return usage ? usage.totalCapacity : 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadStorageData();
|
||||||
|
await this.loadStockTypes();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadStorageData() {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.get(`/api/falukant/storage/${this.branchId}`);
|
||||||
|
this.currentStorage = data.totalUsedCapacity;
|
||||||
|
this.maxStorage = data.maxCapacity;
|
||||||
|
this.storageUsage = data.usageByType;
|
||||||
|
const filteredBuyable = data.buyableByType.filter(item => item.quantity > 0);
|
||||||
|
const grouped = {};
|
||||||
|
filteredBuyable.forEach(item => {
|
||||||
|
const key = item.stockTypeLabelTr;
|
||||||
|
if (!grouped[key]) {
|
||||||
|
grouped[key] = { stockTypeLabelTr: key, totalQuantity: 0, items: [] };
|
||||||
|
}
|
||||||
|
grouped[key].totalQuantity += item.quantity;
|
||||||
|
grouped[key].items.push({ stockTypeId: item.stockTypeId, quantity: item.quantity });
|
||||||
|
});
|
||||||
|
this.buyableUsage = Object.values(grouped);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading storage data:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadStockTypes() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/api/falukant/stocktypes');
|
||||||
|
this.stockTypes = response.data;
|
||||||
|
if (this.stockTypes.length) {
|
||||||
|
this.selectedBuyStockTypeLabelTr = this.stockTypes[0].labelTr;
|
||||||
|
this.selectedSellStockTypeId = this.stockTypes[0].id;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading stock types:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onBuyStorage() {
|
||||||
|
if (!this.branchId || !this.buyStorageAmount || !this.selectedBuyStockTypeLabelTr) return;
|
||||||
|
const group = this.buyableUsage.find(g => g.stockTypeLabelTr === this.selectedBuyStockTypeLabelTr);
|
||||||
|
if (!group) return;
|
||||||
|
let remainingAmount = this.buyStorageAmount;
|
||||||
|
for (const item of group.items) {
|
||||||
|
if (remainingAmount <= 0) break;
|
||||||
|
const toBuy = Math.min(remainingAmount, item.quantity);
|
||||||
|
try {
|
||||||
|
await apiClient.post(`/api/falukant/storage`, {
|
||||||
|
branchId: this.branchId,
|
||||||
|
amount: toBuy,
|
||||||
|
stockTypeId: item.stockTypeId
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert('Error buying storage for one part of the order');
|
||||||
|
}
|
||||||
|
remainingAmount -= toBuy;
|
||||||
|
}
|
||||||
|
if (remainingAmount > 0) {
|
||||||
|
alert(this.$t('falukant.branch.storage.notEnoughAvailable'));
|
||||||
|
}
|
||||||
|
this.loadStorageData();
|
||||||
|
},
|
||||||
|
onSellStorage() {
|
||||||
|
if (!this.branchId || !this.sellStorageAmount || !this.selectedSellStockTypeId) return;
|
||||||
|
apiClient.delete(`/api/falukant/storage`, {
|
||||||
|
data: {
|
||||||
|
branchId: this.branchId,
|
||||||
|
amount: this.sellStorageAmount,
|
||||||
|
stockTypeId: this.selectedSellStockTypeId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => this.loadStorageData())
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('Error selling storage');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCostOfType(labelTr) {
|
||||||
|
const st = this.stockTypes.find(s => s.labelTr === labelTr);
|
||||||
|
return st ? st.cost : 0;
|
||||||
|
},
|
||||||
|
getCostOfTypeById(stockTypeId) {
|
||||||
|
const st = this.stockTypes.find(s => s.id === stockTypeId);
|
||||||
|
return st ? st.cost : 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.storage-section {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.storage-info table, .storage-market table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -42,6 +42,12 @@
|
|||||||
"selectPermissions": "Bitte auswählen",
|
"selectPermissions": "Bitte auswählen",
|
||||||
"confirmDeleteMessage": "Soll das Forum wirklich gelöscht werden?",
|
"confirmDeleteMessage": "Soll das Forum wirklich gelöscht werden?",
|
||||||
"confirmDeleteTitle": "Forum löschen"
|
"confirmDeleteTitle": "Forum löschen"
|
||||||
|
},
|
||||||
|
"falukant": {
|
||||||
|
"edituser": {
|
||||||
|
"success": "Die Änderungen wurden gespeichert.",
|
||||||
|
"error": "Die Änderungen konnten nicht gespeichert werden."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,8 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"money": "Vermögen",
|
"money": "Vermögen",
|
||||||
"age": "Alter",
|
"age": "Alter",
|
||||||
"mainbranch": "Heimatstadt"
|
"mainbranch": "Heimatstadt",
|
||||||
|
"nobleTitle": "Stand"
|
||||||
},
|
},
|
||||||
"productions": {
|
"productions": {
|
||||||
"title": "Produktionen"
|
"title": "Produktionen"
|
||||||
@@ -194,6 +195,55 @@
|
|||||||
"field": "Feldlager"
|
"field": "Feldlager"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"family": {
|
||||||
|
"title": "Familie",
|
||||||
|
"spouse": {
|
||||||
|
"title": "Beziehung",
|
||||||
|
"name": "Name",
|
||||||
|
"age": "Alter",
|
||||||
|
"status": "Status",
|
||||||
|
"none": "Kein Ehepartner vorhanden.",
|
||||||
|
"search": "Ehepartner suchen",
|
||||||
|
"found": "Ehepartner gefunden",
|
||||||
|
"select": "Verloben mit",
|
||||||
|
"marriagecost": "Verlobungskosten",
|
||||||
|
"notice": "Hinweis. Die beiden Ehepartner bekommen beide den Titel, der höher ist.",
|
||||||
|
"accept": "Werbung mit diesem Partner starten",
|
||||||
|
"wooing": {
|
||||||
|
"gifts": "Werbegeschenke",
|
||||||
|
"sendGift": "Werbegeschenk senden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relationships": {
|
||||||
|
"name": "Name"
|
||||||
|
},
|
||||||
|
"children": {
|
||||||
|
"title": "Kinder",
|
||||||
|
"name": "Name",
|
||||||
|
"age": "Alter",
|
||||||
|
"actions": "Aktionen",
|
||||||
|
"none": "Keine Kinder vorhanden.",
|
||||||
|
"detailButton": "Details anzeigen",
|
||||||
|
"addChild": "Kind hinzufügen"
|
||||||
|
},
|
||||||
|
"lovers": {
|
||||||
|
"title": "Liebhaber",
|
||||||
|
"none": "Keine Liebhaber vorhanden.",
|
||||||
|
"affection": "Zuneigung"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"wooing": "In Werbung",
|
||||||
|
"engaged": "Verlobt",
|
||||||
|
"married": "Verheiratet",
|
||||||
|
"single": "Ledig",
|
||||||
|
"widowed": "Verwitwet"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"addSpouse": "Ehepartner hinzufügen",
|
||||||
|
"viewDetails": "Details anzeigen",
|
||||||
|
"remove": "Entfernen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"product": {
|
"product": {
|
||||||
"wheat": "Weizen",
|
"wheat": "Weizen",
|
||||||
"grain": "Getreide",
|
"grain": "Getreide",
|
||||||
@@ -262,6 +312,21 @@
|
|||||||
"low": "Schlecht",
|
"low": "Schlecht",
|
||||||
"verylow": "Sehr schlecht",
|
"verylow": "Sehr schlecht",
|
||||||
"none": "Kein Wissen"
|
"none": "Kein Wissen"
|
||||||
|
},
|
||||||
|
"gifts": {
|
||||||
|
"Gold Coin": "Goldmünze",
|
||||||
|
"Silk Scarf": "Seidenschale",
|
||||||
|
"Exotic Perfume": "Exotisches Parfum",
|
||||||
|
"Crystal Pendant": "Kristallanhänger",
|
||||||
|
"Leather Journal": "Lederjournal",
|
||||||
|
"Fine Wine": "Feiner Wein",
|
||||||
|
"Artisan Chocolate": "Kunsthandwerkliche Schokolade",
|
||||||
|
"Pearl Necklace": "Perlenanhänger",
|
||||||
|
"Rare Painting": "Seltenes Gemälde",
|
||||||
|
"Silver Watch": "Silberuhr",
|
||||||
|
"Cat": "Katze",
|
||||||
|
"Dog": "Hund",
|
||||||
|
"Horse": "Pferd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,12 @@
|
|||||||
"forum": "Forum",
|
"forum": "Forum",
|
||||||
"userrights": "Benutzerrechte",
|
"userrights": "Benutzerrechte",
|
||||||
"interests": "Interessen",
|
"interests": "Interessen",
|
||||||
"falukant": "Falukant"
|
"falukant": "Falukant",
|
||||||
|
"m-falukant": {
|
||||||
|
"logentries": "Log-Einträge",
|
||||||
|
"edituser": "Benutzer bearbeiten",
|
||||||
|
"database": "Datenbank"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"m-friends": {
|
"m-friends": {
|
||||||
"manageFriends": "Freunde verwalten",
|
"manageFriends": "Freunde verwalten",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import AdminInterestsView from '../views/admin/InterestsView.vue';
|
import AdminInterestsView from '../views/admin/InterestsView.vue';
|
||||||
import AdminContactsView from '../views/admin/ContactsView.vue';
|
import AdminContactsView from '../views/admin/ContactsView.vue';
|
||||||
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
||||||
|
import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue'
|
||||||
|
|
||||||
const adminRoutes = [
|
const adminRoutes = [
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,12 @@ const adminRoutes = [
|
|||||||
component: ForumAdminView,
|
component: ForumAdminView,
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/falukant/edituser',
|
||||||
|
name: 'AdminFalukantEditUserView',
|
||||||
|
component: AdminFalukantEditUserView,
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export default adminRoutes;
|
export default adminRoutes;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import BranchView from '../views/falukant/BranchView.vue';
|
|||||||
import Createview from '../views/falukant/CreateView.vue';
|
import Createview from '../views/falukant/CreateView.vue';
|
||||||
import FalukantOverviewView from '../views/falukant/OverviewView.vue';
|
import FalukantOverviewView from '../views/falukant/OverviewView.vue';
|
||||||
import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue';
|
import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue';
|
||||||
|
import FamilyView from '../views/falukant/FamilyView.vue';
|
||||||
|
|
||||||
const falukantRoutes = [
|
const falukantRoutes = [
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,12 @@ const falukantRoutes = [
|
|||||||
component: MoneyHistoryView,
|
component: MoneyHistoryView,
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/falukant/family',
|
||||||
|
name: 'FalukantFamily',
|
||||||
|
component: FamilyView,
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default falukantRoutes;
|
export default falukantRoutes;
|
||||||
|
|||||||
@@ -159,9 +159,9 @@ const store = createStore({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Retrying Daemon WebSocket connection...');
|
console.log('Retrying Daemon WebSocket connection...');
|
||||||
reconnectFn();
|
reconnectFn();
|
||||||
}, 1000); // Retry every second
|
}, 1000);
|
||||||
|
console.log('Retrying Daemon WebSocket connection...');
|
||||||
};
|
};
|
||||||
|
|
||||||
connectDaemonSocket();
|
connectDaemonSocket();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
116
frontend/src/views/admin/falukant/EditUserView.vue
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Edit Falukant User</h1>
|
||||||
|
<div>
|
||||||
|
<label>Username: <input type="text" v-model="user.username" /></label>
|
||||||
|
<label>Character name: <input type="text" v-model="user.characterName" /></label>
|
||||||
|
<button @click="searchUser">Search</button>
|
||||||
|
</div>
|
||||||
|
<ul v-for="user in users" class="user-list">
|
||||||
|
<li @click="selectUser(user)">{{ user.username }} ({{ user.falukantUser[0].character.definedFirstName.name }} {{
|
||||||
|
user.falukantUser[0].character.definedLastName.name }})</li>
|
||||||
|
</ul>
|
||||||
|
<div v-if="editableUser" class="edit-form">
|
||||||
|
<h2>User: {{ editableUser.username }}</h2>
|
||||||
|
<h3>Character-Name: {{ editableUser.falukantData[0].character.definedFirstName.name }} {{
|
||||||
|
editableUser.falukantData[0].character.definedLastName.name }}</h3>
|
||||||
|
<label>Money: <input type="number" v-model="editableUser.falukantData[0].money" /></label>
|
||||||
|
<label>Age: <input type="number" v-model="age" /></label>
|
||||||
|
<label>Noble title:
|
||||||
|
<select v-model="editableUser.falukantData[0].character.title_of_nobility">
|
||||||
|
<option v-for="title in titles" :value="title.id">{{ $t(`falukant.titles.male.${title.labelTr}`) }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>House: <select v-model="editableUser.falukantData[0].house">
|
||||||
|
<option v-for="house in houses" :value="house.id">{{ $t(`${house.labelTr}`) }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button @click="saveUser">Save</button>
|
||||||
|
<button @click="deleteUser">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AdminFalukantEditUserView',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
username: '',
|
||||||
|
characterName: ''
|
||||||
|
},
|
||||||
|
users: [],
|
||||||
|
editableUser: null,
|
||||||
|
age: null,
|
||||||
|
originalAge: null,
|
||||||
|
originalUser: null,
|
||||||
|
titles: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState('falukant', ['user'])
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const titlesResult = await apiClient.get('/api/falukant/nobility/titels');
|
||||||
|
this.titles = titlesResult.data;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async searchUser() {
|
||||||
|
const userResult = await apiClient.post('/api/admin/falukant/searchuser', {
|
||||||
|
userName: this.user.username,
|
||||||
|
characterName: this.user.characterName
|
||||||
|
});
|
||||||
|
this.users = userResult.data;
|
||||||
|
},
|
||||||
|
async selectUser(user) {
|
||||||
|
const userResult = await apiClient.get(`/api/admin/falukant/getuser/${user.id}`);
|
||||||
|
this.editableUser = userResult.data;
|
||||||
|
this.originalUser = JSON.parse(JSON.stringify(this.editableUser));
|
||||||
|
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
|
||||||
|
this.originalAge = this.age;
|
||||||
|
this.users = [];
|
||||||
|
},
|
||||||
|
async saveUser() {
|
||||||
|
const dataToChange = {
|
||||||
|
id: this.editableUser.falukantData[0].id,
|
||||||
|
};
|
||||||
|
if (this.editableUser.falukantData[0].money != this.originalUser.falukantData[0].money) {
|
||||||
|
dataToChange.money = this.editableUser.falukantData[0].money;
|
||||||
|
}
|
||||||
|
if (this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility) {
|
||||||
|
dataToChange.title_of_nobility = this.editableUser.falukantData[0].character.title_of_nobility;
|
||||||
|
}
|
||||||
|
if (this.originalAge != this.age) {
|
||||||
|
dataToChange.age = this.age;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await apiClient.post(`/api/admin/falukant/edituser`, dataToChange);
|
||||||
|
this.$root.$refs.messageDialog.open('tr:admin.falukant.edituser.success');
|
||||||
|
} catch (error) {
|
||||||
|
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteUser() {
|
||||||
|
const dataToChange = {
|
||||||
|
id: this.editableUser.falukantData[0].id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.user-list>li {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #0066ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
287
frontend/src/views/falukant/FamilyView.vue
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
<template>
|
||||||
|
<div class="contenthidden">
|
||||||
|
<StatusBar />
|
||||||
|
<div class="contentscroll">
|
||||||
|
|
||||||
|
<!-- Titel -->
|
||||||
|
<h2>{{ $t('falukant.family.title') }}</h2>
|
||||||
|
|
||||||
|
<!-- Ehepartner -->
|
||||||
|
<div class="spouse-section">
|
||||||
|
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
|
||||||
|
<div v-if="relationships.length > 0">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.family.relationships.name') }}</td>
|
||||||
|
<td>
|
||||||
|
{{ $t('falukant.titles.' + relationships[0].character2.gender + '.' +
|
||||||
|
relationships[0].character2.nobleTitle) }}
|
||||||
|
{{ relationships[0].character2.firstName }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.family.spouse.age') }}</td>
|
||||||
|
<td>{{ relationships[0].character2.age }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.family.spouse.status') }}</td>
|
||||||
|
<td>{{ $t('falukant.family.statuses.' + relationships[0].relationshipType) }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div v-if="relationships[0].relationshipType === 'wooing'">
|
||||||
|
<h3>{{ $t('falukant.family.spouse.wooing.gifts') }}</h3>
|
||||||
|
<table class="spouse-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>{{ $t('falukant.family.spouse.wooing.gift') }}</th>
|
||||||
|
<th>{{ $t('falukant.family.spouse.wooing.value') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="gift in gifts" :key="gift.id">
|
||||||
|
<td><input type="radio" name="gift" :value="gift.id" v-model="selectedGiftId"></td>
|
||||||
|
<td>{{ $t(`falukant.gifts.${gift.name}`) }}</td>
|
||||||
|
<td>{{ formatCost(gift.cost) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<button @click="sendGift" class="button">{{ $t('falukant.family.spouse.wooing.sendGift') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="proposals && proposals.length > 0">
|
||||||
|
<table class="spouse-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.family.spouse.select') }}</th>
|
||||||
|
<th>{{ $t('falukant.family.spouse.name') }}</th>
|
||||||
|
<th>{{ $t('falukant.family.spouse.age') }}</th>
|
||||||
|
<th>{{ $t('falukant.family.spouse.marriagecost') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="proposal in proposals" :key="proposal.id">
|
||||||
|
<td><input type="radio" name="spouse" :value="proposal.proposedCharacterId"
|
||||||
|
v-model="selectedProposalId"></td>
|
||||||
|
<td>{{
|
||||||
|
$t(`falukant.titles.${proposal.proposedCharacterGender}.${proposal.proposedCharacterNobleTitle}`)
|
||||||
|
}} {{ proposal.proposedCharacterName }}</td>
|
||||||
|
<td>{{ proposal.proposedCharacterAge }}</td>
|
||||||
|
<td>{{ formatCost(proposal.cost) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>{{ $t('falukant.family.spouse.notice') }}</div>
|
||||||
|
<div v-if="selectedProposalId">
|
||||||
|
<button @click="acceptProposal">{{ $t('falukant.family.spouse.accept') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Kinder -->
|
||||||
|
<div class="children-section">
|
||||||
|
<h3>{{ $t('falukant.family.children.title') }}</h3>
|
||||||
|
<div v-if="children && children.length > 0">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('falukant.family.children.name') }}</th>
|
||||||
|
<th>{{ $t('falukant.family.children.age') }}</th>
|
||||||
|
<th>{{ $t('falukant.family.children.actions') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(child, index) in children" :key="index">
|
||||||
|
<td>
|
||||||
|
{{ $t('falukant.titles.' + child.gender + '.' + child.title) }}
|
||||||
|
{{ child.name }}
|
||||||
|
</td>
|
||||||
|
<td>{{ child.age }}</td>
|
||||||
|
<td>
|
||||||
|
<button @click="showChildDetails(child)">
|
||||||
|
{{ $t('falukant.family.children.detailButton') }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p>{{ $t('falukant.family.children.none') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Liebhaber / Geliebte -->
|
||||||
|
<div class="lovers-section">
|
||||||
|
<h3>{{ $t('falukant.family.lovers.title') }}</h3>
|
||||||
|
<div v-if="lovers && lovers.length > 0">
|
||||||
|
<ul>
|
||||||
|
<li v-for="(lover, idx) in lovers" :key="idx">
|
||||||
|
{{ $t('falukant.titles.' + lover.gender + '.' + lover.title) }} {{ lover.name }}
|
||||||
|
({{ $t('falukant.family.lovers.affection') }}: {{ lover.affection }})
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p>{{ $t('falukant.family.lovers.none') }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Dialog-Beispiele oder ähnliche Komponenten -->
|
||||||
|
<MessageDialog ref="messageDialog" />
|
||||||
|
<ErrorDialog ref="errorDialog" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import StatusBar from '@/components/falukant/StatusBar.vue'
|
||||||
|
import MessageDialog from '@/dialogues/standard/MessageDialog.vue'
|
||||||
|
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue'
|
||||||
|
|
||||||
|
import apiClient from '@/utils/axios.js'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FamilyView',
|
||||||
|
components: {
|
||||||
|
StatusBar,
|
||||||
|
MessageDialog,
|
||||||
|
ErrorDialog
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
relationships: [],
|
||||||
|
children: [],
|
||||||
|
lovers: [],
|
||||||
|
deathPartners: [],
|
||||||
|
proposals: [],
|
||||||
|
selectedProposalId: null,
|
||||||
|
gifts: [],
|
||||||
|
selectedGiftId: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['socket', 'daemonSocket'])
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadFamilyData();
|
||||||
|
await this.loadGifts();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadFamilyData() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/api/falukant/family');
|
||||||
|
console.log(response.data);
|
||||||
|
this.relationships = response.data.relationships;
|
||||||
|
this.children = response.data.children;
|
||||||
|
this.lovers = response.data.lovers;
|
||||||
|
this.proposals = response.data.possiblePartners;
|
||||||
|
this.deathPartners = response.data.deathPartners;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading family data:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showChildDetails(child) {
|
||||||
|
console.log('Show details for child:', child);
|
||||||
|
},
|
||||||
|
|
||||||
|
formatCost(value) {
|
||||||
|
return new Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
async acceptProposal() {
|
||||||
|
const response = await apiClient.post('/api/falukant/family/acceptmarriageproposal'
|
||||||
|
, { proposalId: this.selectedProposalId });
|
||||||
|
this.loadFamilyData();
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadGifts() {
|
||||||
|
const response = await apiClient.get('/api/falukant/family/gifts');
|
||||||
|
this.gifts = response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendGift() {
|
||||||
|
if (!this.selectedGiftId) {
|
||||||
|
alert('Please select a gift');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await apiClient.post('/api/falukant/family/gift'
|
||||||
|
, { giftId: this.selectedGiftId });
|
||||||
|
this.loadFamilyData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.spouse-section,
|
||||||
|
.children-section,
|
||||||
|
.lovers-section {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-section table,
|
||||||
|
.children-section table {
|
||||||
|
margin-top: 10px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-section th,
|
||||||
|
.spouse-section td,
|
||||||
|
.children-section th,
|
||||||
|
.children-section td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-section th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.children-section th {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lovers-section {
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-table th,
|
||||||
|
.spouse-table td {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-table th:first-child,
|
||||||
|
.spouse-table td:first-child {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-table th:nth-child(3),
|
||||||
|
.spouse-table td:nth-child(3) {
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spouse-table th:nth-child(4),
|
||||||
|
.spouse-table td:nth-child(4) {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="moneyflow">
|
||||||
|
<StatusBar ref="statusBar" />
|
||||||
<h2>{{ $t('falukant.moneyHistory.title') }}</h2>
|
<h2>{{ $t('falukant.moneyHistory.title') }}</h2>
|
||||||
|
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
@@ -30,19 +31,14 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<button
|
<button v-if="moneyHistory.currentPage > 1" @click="fetchMoneyHistory(moneyHistory.currentPage - 1)">
|
||||||
v-if="moneyHistory.currentPage > 1"
|
|
||||||
@click="fetchMoneyHistory(moneyHistory.currentPage - 1)"
|
|
||||||
>
|
|
||||||
{{ $t('falukant.moneyHistory.prev') }}
|
{{ $t('falukant.moneyHistory.prev') }}
|
||||||
</button>
|
</button>
|
||||||
<span>
|
<span>
|
||||||
{{ moneyHistory.currentPage }} / {{ moneyHistory.totalPages }}
|
{{ moneyHistory.currentPage }} / {{ moneyHistory.totalPages }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button v-if="moneyHistory.currentPage < moneyHistory.totalPages"
|
||||||
v-if="moneyHistory.currentPage < moneyHistory.totalPages"
|
@click="fetchMoneyHistory(moneyHistory.currentPage + 1)">
|
||||||
@click="fetchMoneyHistory(moneyHistory.currentPage + 1)"
|
|
||||||
>
|
|
||||||
{{ $t('falukant.moneyHistory.next') }}
|
{{ $t('falukant.moneyHistory.next') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,10 +46,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import StatusBar from '@/components/falukant/StatusBar.vue'
|
||||||
import apiClient from '@/utils/axios.js';
|
import apiClient from '@/utils/axios.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MoneyHistoryView',
|
name: 'MoneyHistoryView',
|
||||||
|
components: {
|
||||||
|
StatusBar,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
filter: '',
|
filter: '',
|
||||||
@@ -87,20 +87,32 @@
|
|||||||
.filter-section {
|
.filter-section {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
th, td {
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moneyflow {
|
||||||
|
overflow: auto;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,9 +11,17 @@
|
|||||||
<td>{{ falukantUser?.character.definedFirstName.name }} {{
|
<td>{{ falukantUser?.character.definedFirstName.name }} {{
|
||||||
falukantUser?.character.definedLastName.name }}</td>
|
falukantUser?.character.definedLastName.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('falukant.overview.metadata.nobleTitle') }}</td>
|
||||||
|
<td>{{ $t('falukant.titles.' + falukantUser?.character.gender + '.' + falukantUser?.character.nobleTitle.labelTr) }}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.money') }}</td>
|
<td>{{ $t('falukant.overview.metadata.money') }}</td>
|
||||||
<td>{{ falukantUser?.money }}</td>
|
<td>
|
||||||
|
{{ moneyValue != null
|
||||||
|
? moneyValue.toLocaleString(locale, { style: 'currency', currency: 'EUR' })
|
||||||
|
: '---' }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.age') }}</td>
|
<td>{{ $t('falukant.overview.metadata.age') }}</td>
|
||||||
@@ -160,6 +168,13 @@ export default {
|
|||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
moneyValue() {
|
||||||
|
const m = this.falukantUser?.money;
|
||||||
|
return typeof m === 'string' ? parseFloat(m) : m;
|
||||||
|
},
|
||||||
|
locale() {
|
||||||
|
return window.navigator.language || 'en-US';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.fetchFalukantUser();
|
await this.fetchFalukantUser();
|
||||||
@@ -171,7 +186,6 @@ export default {
|
|||||||
}
|
}
|
||||||
if (this.daemonSocket) {
|
if (this.daemonSocket) {
|
||||||
this.daemonSocket.addEventListener('message', (event) => {
|
this.daemonSocket.addEventListener('message', (event) => {
|
||||||
console.log('incoming event', event);
|
|
||||||
try {
|
try {
|
||||||
if (event.data === "ping") return;
|
if (event.data === "ping") return;
|
||||||
const message = JSON.parse(event.data);
|
const message = JSON.parse(event.data);
|
||||||
@@ -182,6 +196,8 @@ export default {
|
|||||||
console.error('Error processing WebSocket message in FalukantOverviewView:', error);
|
console.error('Error processing WebSocket message in FalukantOverviewView:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log('no daemon socket');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
@@ -281,4 +297,8 @@ export default {
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
image-rendering: crisp-edges;
|
image-rendering: crisp-edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 35 KiB |