Files
yourpart3/backend/services/falukantService.js
2025-01-28 09:55:36 +01:00

992 lines
40 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import BaseService from './BaseService.js';
import { Sequelize, Op } from 'sequelize';
import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js';
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
import FalukantUser from '../models/falukant/data/user.js';
import FalukantCharacter from '../models/falukant/data/character.js';
import RegionData from '../models/falukant/data/region.js';
import RegionType from '../models/falukant/type/region.js';
import FalukantStock from '../models/falukant/data/stock.js';
import FalukantStockType from '../models/falukant/type/stock.js';
import TitleOfNobility from '../models/falukant/type/title_of_nobility.js';
import Branch from '../models/falukant/data/branch.js';
import BranchType from '../models/falukant/type/branch.js';
import Production from '../models/falukant/data/production.js';
import ProductType from '../models/falukant/type/product.js';
import Knowledge from '../models/falukant/data/product_knowledge.js';
import Inventory from '../models/falukant/data/inventory.js';
import MoneyFlow from '../models/falukant/log/moneyflow.js';
import User from '../models/community/user.js';
import { notifyUser } from '../utils/socket.js';
import { differenceInDays } from 'date-fns';
import { updateFalukantUserMoney } from '../utils/sequelize.js';
import BuyableStock from '../models/falukant/data/buyable_stock.js';
import DirectorProposal from '../models/falukant/data/director_proposal.js';
import Director from '../models/falukant/data/director.js';
import DaySell from '../models/falukant/log/daysell.js';
function calcAge(birthdate) {
const b = new Date(birthdate); b.setHours(0, 0);
const now = new Date(); now.setHours(0, 0);
return differenceInDays(now, b);
}
async function getFalukantUserOrFail(hashedId) {
const user = await FalukantUser.findOne({
include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }]
});
if (!user) throw new Error('User not found');
return user;
}
async function getBranchOrFail(userId, branchId) {
const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: userId } });
if (!branch) throw new Error('Branch not found');
return branch;
}
function calcSellPrice(product, knowledgeFactor = 0) {
const max = product.sellCost;
const min = max * 0.6;
return min + (max - min) * (knowledgeFactor / 100);
}
class FalukantService extends BaseService {
async getFalukantUserByHashedId(hashedId) {
return FalukantUser.findOne({
include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }]
});
}
async getUser(hashedUserId) {
const u = await FalukantUser.findOne({
include: [
{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId: hashedUserId } },
{
model: FalukantCharacter,
as: 'character',
include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] },
{ model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] }
],
attributes: ['birthdate', 'gender']
},
{
model: RegionData,
as: 'mainBranchRegion',
include: [{ model: RegionType, as: 'regionType' }],
attributes: ['name']
},
{
model: Branch,
as: 'branches',
include: [
{ model: BranchType, as: 'branchType', attributes: ['labelTr'] },
{
model: RegionData,
as: 'region',
include: [{ model: RegionType, as: 'regionType' }],
attributes: ['name']
}
]
}
],
attributes: ['money', 'creditAmount', 'todayCreditTaken']
});
if (!u) throw new Error('User not found');
if (u.character?.birthdate) u.character.setDataValue('age', calcAge(u.character.birthdate));
return u;
}
async randomFirstName(gender) {
const names = await FalukantPredefineFirstname.findAll({ where: { gender } });
return names[Math.floor(Math.random() * names.length)].name;
}
async randomLastName() {
const names = await FalukantPredefineLastname.findAll();
return names[Math.floor(Math.random() * names.length)].name;
}
async createUser(hashedUserId, gender, firstName, lastName) {
const user = await this.getUserByHashedId(hashedUserId);
if (await FalukantUser.findOne({ where: { userId: user.id } })) throw new Error('User already exists in Falukant.');
let fnObj = await FalukantPredefineFirstname.findOne({ where: { name: firstName } });
let lnObj = await FalukantPredefineLastname.findOne({ where: { name: lastName } });
if (!fnObj) fnObj = await FalukantPredefineFirstname.create({ name: firstName, gender });
if (!lnObj) lnObj = await FalukantPredefineLastname.create({ name: lastName });
const region = await RegionData.findOne({
order: Sequelize.fn('RANDOM'),
limit: 1,
include: [{ model: RegionType, as: 'regionType', where: { labelTr: 'city' } }]
});
if (!region) throw new Error('No region found with the label "city".');
const nobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } });
if (!nobility) throw new Error('No title of nobility found with the label "noncivil".');
const falukantUser = await FalukantUser.create({
userId: user.id, money: 50, creditAmount: 0, todayCreditTaken: 0, creditInterestRate: 0, mainBranchRegionId: region.id
});
const date = new Date(); date.setDate(date.getDate() - 14);
const ch = await FalukantCharacter.create({
userId: falukantUser.id, regionId: region.id, firstName: fnObj.id, lastName: lnObj.id, gender, birthdate: date, titleOfNobility: nobility.id
});
const stType = await FalukantStockType.findOne({ where: [{ label_tr: 'wood' }] });
await FalukantStock.create({ userId: falukantUser.id, regionId: region.id, stockTypeId: stType.id, quantity: 10 });
falukantUser.character = ch;
const bType = await BranchType.findOne({ where: { labelTr: 'fullstack' } });
await Branch.create({ userId: falukantUser.id, regionId: region.id, branchTypeId: bType.id });
notifyUser(user.hashedId, 'reloadmenu', {});
return falukantUser;
}
async getInfo(hashedUserId) {
const user = await User.findOne({ where: { hashedId: hashedUserId } });
if (!user) throw new Error('User not found');
const falukantUser = await FalukantUser.findOne({
include: [
{ model: User, as: 'user', attributes: ['hashedId'], where: { hashedId: hashedUserId } },
{ model: FalukantCharacter, as: 'character', attributes: ['birthdate', 'health'] }
],
attributes: ['money']
});
if (!falukantUser) throw new Error('User not found');
if (falukantUser.character?.birthdate) falukantUser.character.setDataValue('age', calcAge(falukantUser.character.birthdate));
return falukantUser;
}
async getBranches(hashedUserId) {
const u = await getFalukantUserOrFail(hashedUserId);
const bs = await Branch.findAll({
where: { falukantUserId: u.id },
include: [
{ model: BranchType, as: 'branchType', attributes: ['labelTr'] },
{ model: RegionData, as: 'region', attributes: ['name'] }
],
attributes: ['id', 'regionId'],
order: [['branchTypeId', 'ASC']]
});
return bs.map(b => ({ ...b.toJSON(), isMainBranch: u.mainBranchRegionId === b.regionId }));
}
async getBranch(hashedUserId, branchId) {
const u = await getFalukantUserOrFail(hashedUserId);
const br = await Branch.findOne({
where: { id: branchId, falukantUserId: u.id },
include: [
{ model: BranchType, as: 'branchType', attributes: ['labelTr'] },
{ model: RegionData, as: 'region', attributes: ['name'] },
{
model: Production,
as: 'productions',
attributes: ['quantity', 'startTimestamp'],
include: [{ model: ProductType, as: 'productType', attributes: ['id', 'category', 'labelTr', 'sellCost', 'productionTime'] }]
}
],
attributes: ['id', 'regionId']
});
if (!br) throw new Error('Branch not found');
return br;
}
async getStock(hashedUserId, branchId) {
const u = await getFalukantUserOrFail(hashedUserId);
const b = await getBranchOrFail(u.id, branchId);
return FalukantStock.findAll({ where: { regionId: b.regionId, userId: u.id } });
}
async createStock(hashedUserId, branchId, stockData) {
const u = await getFalukantUserOrFail(hashedUserId);
const b = await getBranchOrFail(u.id, branchId);
return FalukantStock.create({
userId: u.id, regionId: b.regionId, stockTypeId: stockData.stockTypeId, quantity: stockData.quantity
});
}
async createProduction(hashedUserId, branchId, productId, quantity) {
const u = await getFalukantUserOrFail(hashedUserId);
const b = await getBranchOrFail(u.id, branchId);
const p = await ProductType.findOne({ where: { id: productId } });
if (!p) throw new Error('Product not found');
const cost = quantity * p.category * 6;
if (u.money < cost) throw new Error('notenoughmoney');
const r = await updateFalukantUserMoney(u.id, -cost, 'Production cost', u.id);
if (!r.success) throw new Error('Failed to update money');
const d = await Production.create({ branchId: b.id, productId, quantity });
notifyUser(u.user.hashedId, 'falukantUpdateStatus', {});
notifyUser(u.user.hashedId, 'falukantBranchUpdate', { branchId: b.id });
return d;
}
async getProduction(hashedUserId, branchId) {
const u = await getFalukantUserOrFail(hashedUserId);
const b = await getBranchOrFail(u.id, branchId);
return Production.findOne({ where: { regionId: b.regionId } });
}
async getProducts(hashedUserId) {
const u = await getFalukantUserOrFail(hashedUserId);
console.log(u);
const c = await FalukantCharacter.findOne({ where: { userId: u.id } });
console.log(c);
if (!c) {
throw new Error(`No FalukantCharacter found for user with id ${u.id}`);
}
const ps = await ProductType.findAll({
where: { category: { [Op.lte]: u.certificate } },
include: [{ model: Knowledge, as: 'knowledges', attributes: ['knowledge'], where: { characterId: c.id } }],
attributes: ['labelTr', 'id', 'sellCost', 'productionTime', 'category']
});
return ps;
}
async getInventory(hashedUserId, branchId) {
const u = await getFalukantUserOrFail(hashedUserId);
const f = branchId ? { id: branchId, falukantUserId: u.id } : { falukantUserId: u.id };
const br = await Branch.findAll({
where: f,
include: [
{ model: FalukantStock, as: 'stocks', include: [{ model: FalukantStockType, as: 'stockType' }] },
{ model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }
]
});
const stockIds = br.flatMap(b => b.stocks.map(s => s.id));
const inv = await Inventory.findAll({
where: { stockId: stockIds },
include: [
{
model: FalukantStock,
as: 'stock',
include: [
{
model: Branch,
as: 'branch',
include: [{ model: RegionData, as: 'region', include: [{ model: RegionType, as: 'regionType' }] }]
},
{ model: FalukantStockType, as: 'stockType' }
]
},
{ model: ProductType, as: 'productType' }
]
});
const grouped = inv.reduce((acc, i) => {
const r = i.stock.branch.region;
const k = `${r.id}-${i.productType.id}-${i.quality}`;
acc[k] = acc[k] || { region: r, product: i.productType, quality: i.quality, totalQuantity: 0 };
acc[k].totalQuantity += i.quantity;
return acc;
}, {});
return Object.values(grouped).sort((a, b) => {
if (a.region.id !== b.region.id) return a.region.id - b.region.id;
if (a.product.id !== b.product.id) return a.product.id - b.product.id;
return a.quality - b.quality;
});
}
async sellProduct(hashedUserId, branchId, productId, quality, quantity) {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await getBranchOrFail(user.id, branchId);
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
if (!character) throw new Error('No character found for user');
const stock = await FalukantStock.findOne({ where: { branchId: branch.id } });
if (!stock) throw new Error('Stock not found');
const inventory = await Inventory.findAll({
where: { stockId: stock.id, quality },
include: [
{
model: ProductType,
as: 'productType',
required: true,
where: { id: productId },
include: [
{
model: Knowledge,
as: 'knowledges',
required: false,
where: { characterId: character.id }
}
]
}
]
});
if (!inventory.length) throw new Error('No inventory found');
const available = inventory.reduce((sum, i) => sum + i.quantity, 0);
if (available < quantity) throw new Error('Not enough inventory available');
const item = inventory[0].productType;
const knowledgeVal = item.knowledges?.[0]?.knowledge || 0;
const revenue = quantity * calcSellPrice(item, knowledgeVal);
const moneyResult = await updateFalukantUserMoney(user.id, revenue, 'Product sale', user.id);
if (!moneyResult.success) throw new Error('Failed to update money');
let remaining = quantity;
for (const inv of inventory) {
if (inv.quantity <= remaining) {
remaining -= inv.quantity;
await inv.destroy();
} else {
await inv.update({ quantity: inv.quantity - remaining });
remaining = 0;
break;
}
}
await this.addSellItem(branchId, falukantUser.id, productId, quantity);
notifyUser(user.user.hashedId, 'falukantUpdateStatus', {});
notifyUser(user.user.hashedId, 'falukantBranchUpdate', { branchId: branch.id });
return { success: true };
}
async sellAllProducts(hashedUserId, branchId) {
const falukantUser = await getFalukantUserOrFail(hashedUserId);
const branch = await Branch.findOne({
where: { id: branchId, falukantUserId: falukantUser.id },
include: [{ model: FalukantStock, as: 'stocks' }]
});
if (!branch) throw new Error('Branch not found');
const stockIds = branch.stocks.map(s => s.id);
const character = await FalukantCharacter.findOne({ where: { userId: falukantUser.id } });
if (!character) throw new Error('No character for user');
const inventory = await Inventory.findAll({
where: { stockId: stockIds },
include: [
{
model: ProductType,
as: 'productType',
include: [
{
model: Knowledge,
as: 'knowledges',
required: false,
where: {
characterId: character.id
}
}
]
},
{
model: FalukantStock,
as: 'stock',
include: [
{
model: Branch,
as: 'branch'
},
{
model: FalukantStockType,
as: 'stockType'
}
]
}
]
});
if (!inventory.length) return { success: true, revenue: 0 };
let total = 0;
for (const item of inventory) {
const knowledgeVal = item.productType.knowledges[0]?.knowledge || 0;
total += item.quantity * calcSellPrice(item.productType, knowledgeVal);
await this.addSellItem(item.stock[0].branch[0].id, falukantUser.id, item.productType.id, item.quantity);
}
const moneyResult = await updateFalukantUserMoney(
falukantUser.id,
total,
'Sell all products',
falukantUser.id
);
if (!moneyResult.success) throw new Error('Failed to update money');
for (const item of inventory) {
await Inventory.destroy({ where: { id: item.id } });
}
notifyUser(falukantUser.user.hashedId, 'falukantUpdateStatus', {});
notifyUser(falukantUser.user.hashedId, 'falukantBranchUpdate', {});
return { success: true, revenue: total };
}
async addSellItem(branchId, userId, productId, quantity) {
const branch = await Branch.findOne({
where: { id: branchId },
})
;
const daySell = await DaySell.findOne({
where: {
regionId: regionId,
productId: productId,
sellerId: userId,
}
});
if (daySell) {
daySell.quantity += quantity;
await daySell.save();
} else {
await DaySell.create({
regionId: regionId,
productId: productId,
sellerId: userId,
quantity: quantity,
});
}
}
async moneyHistory(hashedUserId, page = 1, filter = '') {
const u = await getFalukantUserOrFail(hashedUserId);
const limit = 25, offset = (page - 1) * limit;
const w = { falukantUserId: u.id };
if (filter) w.activity = { [Op.iLike]: `%${filter}%` };
const { rows, count } = await MoneyFlow.findAndCountAll({
where: w, order: [['time', 'DESC']], limit, offset
});
return { data: rows, total: count, currentPage: page, totalPages: Math.ceil(count / limit) };
}
async getStorage(hashedUserId, branchId) {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await getBranchOrFail(user.id, branchId);
const stocks = await FalukantStock.findAll({
where: { branchId: branch.id },
include: [{ model: FalukantStockType, as: 'stockType' }],
});
const stockIds = stocks.map(s => s.id);
const inventoryItems = await Inventory.findAll({
where: { stockId: stockIds },
include: [
{
model: FalukantStock,
as: 'stock',
include: [{ model: FalukantStockType, as: 'stockType' }],
},
],
});
let totalUsedCapacity = 0;
const usageByType = {};
for (const s of stocks) {
const stId = s.stockTypeId;
if (!usageByType[stId]) {
usageByType[stId] = {
stockTypeId: stId,
stockTypeLabelTr: s.stockType?.labelTr || `stockType:${stId}`,
totalCapacity: 0,
used: 0
};
}
usageByType[stId].totalCapacity += s.quantity;
}
for (const item of inventoryItems) {
totalUsedCapacity += item.quantity;
const stId = item.stock.stockTypeId;
if (!usageByType[stId]) {
usageByType[stId] = {
stockTypeId: stId,
stockTypeLabelTr: item.stock.stockType?.labelTr || `stockType:${stId}`,
totalCapacity: 0,
used: 0
};
}
usageByType[stId].used += item.quantity;
}
const buyableStocks = await BuyableStock.findAll({
where: { regionId: branch.regionId },
include: [{ model: FalukantStockType, as: 'stockType' }],
});
const buyableByType = {};
for (const b of buyableStocks) {
const stId = b.stockTypeId;
if (!buyableByType[stId]) {
buyableByType[stId] = {
stockTypeId: stId,
stockTypeLabelTr: b.stockType?.labelTr || `stockType:${stId}`,
quantity: 0
};
}
buyableByType[stId].quantity += b.quantity;
}
let maxCapacity = stocks.reduce((sum, s) => sum + s.quantity, 0);
return {
branchId,
totalUsedCapacity,
maxCapacity,
usageByType: Object.values(usageByType),
buyableByType: Object.values(buyableByType)
};
}
async buyStorage(hashedUserId, branchId, amount, stockTypeId) {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await getBranchOrFail(user.id, branchId);
const buyable = await BuyableStock.findOne({
where: { regionId: branch.regionId, stockTypeId },
include: [{ model: FalukantStockType, as: 'stockType' }]
});
if (!buyable || buyable.quantity < amount) throw new Error('Not enough buyable stock');
const costPerUnit = buyable.stockType.cost;
const totalCost = costPerUnit * amount;
if (user.money < totalCost) throw new Error('notenoughmoney');
const moneyResult = await updateFalukantUserMoney(
user.id,
-totalCost,
`Buy storage (type: ${buyable.stockType.labelTr})`,
user.id
);
if (!moneyResult.success) throw new Error('Failed to update money');
buyable.quantity -= amount;
await buyable.save();
const stock = await FalukantStock.findOne({
where: { branchId: branch.id, stockTypeId },
include: [{ model: FalukantStockType, as: 'stockType' }]
});
if (!stock) throw new Error('No stock record found for this branch and stockType');
stock.quantity += amount;
await stock.save();
return { success: true, bought: amount, totalCost, stockType: buyable.stockType.labelTr };
}
async sellStorage(hashedUserId, branchId, amount, stockTypeId) {
const user = await getFalukantUserOrFail(hashedUserId);
const branch = await getBranchOrFail(user.id, branchId);
const stock = await FalukantStock.findOne({
where: { branchId: branch.id, stockTypeId },
include: [{ model: FalukantStockType, as: 'stockType' }]
});
if (!stock || stock.quantity < amount) throw new Error('Not enough stock to sell');
const costPerUnit = stock.stockType.cost;
const totalRevenue = costPerUnit * amount;
const moneyResult = await updateFalukantUserMoney(
user.id,
totalRevenue,
`Sell storage (type: ${stock.stockType.labelTr})`,
user.id
);
if (!moneyResult.success) throw new Error('Failed to update money');
stock.quantity -= amount;
await stock.save();
const buyable = await BuyableStock.findOne({
where: { regionId: branch.regionId, stockTypeId },
include: [{ model: FalukantStockType, as: 'stockType' }]
});
if (!buyable) throw new Error('No buyable record found for this region and stockType');
buyable.quantity += amount;
await buyable.save();
return { success: true, sold: amount, totalRevenue, stockType: stock.stockType.labelTr };
}
async notifyRegionUsersAboutStockChange(regionId) {
const users = await FalukantCharacter.findAll({
where: {
regionId: regionId
},
include: [
{
model: FalukantUser,
as: 'user',
include: [
{
model: User,
as: 'user'
},
{
model: Branch,
as: 'branch',
where: {
regionId: regionId
}
}
]
}
]
});
for (const user of users) {
notifyUser(user.user[0].user[0].hashedId, 'stock_change', { branchId: user.user[0].branch[0].id });
}
}
async getStockTypes() {
return FalukantStockType.findAll();
}
async getStockOverview() {
const items = await Inventory.findAll({
include: [
{
model: FalukantStock,
as: 'stock',
include: [
{
model: Branch,
as: 'branch',
include: [{ model: RegionData, as: 'region' }]
}
]
},
{
model: ProductType,
as: 'productType'
}
]
});
const result = items.map(inv => ({
regionName: inv.stock?.branch?.region?.name || '???',
productLabelTr: inv.productType?.labelTr || '???',
quantity: inv.quantity
}));
return result;
}
async getAllProductions(hashedUserId) {
const user = await getFalukantUserOrFail(hashedUserId);
const productions = await Production.findAll({
include: [
{
model: Branch,
as: 'branch',
include: [
{ model: RegionData, as: 'region', attributes: ['name'] }
],
where: { falukantUserId: user.id }
},
{ model: ProductType, as: 'productType', attributes: ['labelTr', 'productionTime'] }
],
attributes: ['startTimestamp', 'quantity'],
});
const formattedProductions = productions.map((production) => {
const startTimestamp = new Date(production.startTimestamp).getTime();
const endTimestamp = startTimestamp + production.productType.productionTime * 60 * 1000;
return {
cityName: production.branch.region.name,
productName: production.productType.labelTr,
quantity: production.quantity,
endTimestamp: new Date(endTimestamp).toISOString(),
};
});
formattedProductions.sort((a, b) => new Date(a.endTimestamp) - new Date(b.endTimestamp));
return formattedProductions;
}
async getDirectorProposals(hashedUserId, branchId) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
if (!user) {
throw new Error('User not found');
}
const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id } });
if (!branch) {
throw new Error('Branch not found or does not belong to the user');
}
const { falukantUserId, regionId } = branch;
await this.deleteExpiredProposals();
const existingProposals = await this.fetchProposals(falukantUserId, regionId);
if (existingProposals.length > 0) {
return this.formatProposals(existingProposals);
}
await this.generateProposals(falukantUserId, regionId);
const newProposals = await this.fetchProposals(falukantUserId, regionId);
return this.formatProposals(newProposals);
}
async deleteExpiredProposals() {
const expirationTime = new Date(Date.now() - 24 * 60 * 60 * 1000);
await DirectorProposal.destroy({
where: {
createdAt: {
[Op.lt]: expirationTime,
},
},
});
}
async fetchProposals(falukantUserId, regionId) {
return DirectorProposal.findAll({
where: { employerUserId: falukantUserId },
include: [
{
model: FalukantCharacter,
as: 'character',
attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'],
where: { regionId },
include: [
{ model: FalukantPredefineFirstname, as: 'definedFirstName' },
{ model: FalukantPredefineLastname, as: 'definedLastName' },
{ model: TitleOfNobility, as: 'nobleTitle' },
{
model: Knowledge,
as: 'knowledges',
include: [
{ model: ProductType, as: 'productType' },
]
},
],
},
],
});
}
async generateProposals(falukantUserId, regionId) {
const proposalCount = Math.floor(Math.random() * 3) + 3;
for (let i = 0; i < proposalCount; i++) {
const directorCharacter = await FalukantCharacter.findOne({
where: {
regionId,
createdAt: {
[Op.lt]: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000),
},
},
order: Sequelize.fn('RANDOM'),
});
if (!directorCharacter) {
throw new Error('No directors available for the region');
}
const avgKnowledge = await this.calculateAverageKnowledge(directorCharacter.id);
const proposedIncome = Math.round(
directorCharacter.titleOfNobility * Math.pow(1.231, avgKnowledge / 1.5)
);
await DirectorProposal.create({
directorCharacterId: directorCharacter.id,
employerUserId: falukantUserId,
proposedIncome,
});
}
}
async calculateAverageKnowledge(characterId) {
const averageKnowledge = await Knowledge.findAll({
where: { characterId },
attributes: [[Sequelize.fn('AVG', Sequelize.col('knowledge')), 'avgKnowledge']],
raw: true,
});
return parseFloat(averageKnowledge[0]?.avgKnowledge || 0);
}
formatProposals(proposals) {
return proposals.map((proposal) => {
const age = Math.floor((Date.now() - new Date(proposal.character.birthdate)) / (24 * 60 * 60 * 1000));
const knowledge = proposal.character.knowledges?.map(k => ({
productId: k.productId,
value: k.knowledge,
labelTr: k.productType.labelTr,
})) || [];
return {
id: proposal.id,
proposedIncome: proposal.proposedIncome,
character: {
name: `${proposal.character.definedFirstName.name} ${proposal.character.definedLastName.name}`,
title: proposal.character.nobleTitle.labelTr,
age,
knowledge,
gender: proposal.character.gender,
},
};
});
}
async convertProposalToDirector(hashedUserId, proposalId) {
const user = await getFalukantUserOrFail(hashedUserId);
if (!user) {
throw new Error('User not found');
}
const proposal = await DirectorProposal.findOne(
{
where: { id: proposalId },
include: [
{ model: FalukantCharacter, as: 'character' },
]
}
);
if (!proposal || proposal.employerUserId !== user.id) {
throw new Error('Proposal does not belong to the user');
}
const existingDirector = await Director.findOne({
where: {
employerUserId: user.id
},
include: [
{
model: FalukantCharacter,
as: 'character',
where: {
regionId: proposal.character.regionId,
}
},
]
});
if (existingDirector) {
throw new Error('A director already exists for this region');
}
const newDirector = await Director.create({
directorCharacterId: proposal.directorCharacterId,
employerUserId: proposal.employerUserId,
income: proposal.proposedIncome,
});
const regionUserDirectorProposals = await DirectorProposal.findAll({
where: {
employerUserId: proposal.employerUserId,
},
include: [
{
model: FalukantCharacter,
as: 'character',
where: {
regionId: proposal.character.regionId,
}
},
]
});
if (regionUserDirectorProposals.length > 0) {
for (const proposal of regionUserDirectorProposals) {
await DirectorProposal.destroy();
}
}
notifyUser(hashedUserId, 'directorchanged');
return newDirector;
}
async getDirectorForBranch(hashedUserId, branchId) {
const user = await getFalukantUserOrFail(hashedUserId);
if (!user) {
throw new Error('User not found');
}
const branch = await Branch.findOne({ where: { id: branchId, falukantUserId: user.id } });
if (!branch) {
throw new Error('Branch not found or does not belong to the user');
}
const director = await Director.findOne({
where: { employerUserId: user.id },
include: [
{
model: FalukantCharacter,
as: 'character',
attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'],
where: {
regionId: branch.regionId,
},
include: [
{
model: TitleOfNobility,
as: 'nobleTitle'
},
{
model: FalukantPredefineFirstname,
as: 'definedFirstName'
},
{
model: FalukantPredefineLastname,
as: 'definedLastName'
},
]
},
],
});
if (!director) {
return null;
}
const age = Math.floor((Date.now() - new Date(director.character.birthdate)) / (24 * 60 * 60 * 1000));
return {
director: {
id: director.id,
character: {
name: `${director.character.definedFirstName.name} ${director.character.definedLastName.name}`,
title: director.character.nobleTitle.labelTr,
age,
gender: director.character.gender,
},
income: director.income,
satisfaction: director.satisfaction,
mayProduce: director.mayProduce,
maySell: director.maySell,
mayStartTransport: director.mayStartTransport,
},
};
}
async setSetting(hashedUserId, branchId, directorId, settingKey, value) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
const branch = await Branch.findOne({
where: {
id: branchId,
falukantUserId: user.id,
},
});
if (!branch) {
return null;
}
const director = await Director.findOne({
where: {
id: directorId,
employerUserId: user.id,
},
include: [{
model: FalukantCharacter,
as: 'character',
where: {
regionId: branch.regionId,
}
}]
});
if (!director) {
return null;
}
const updateData = {};
updateData[settingKey] = value || false;
await Director.update(updateData, {
where: {
id: director.id,
},
});
return { result: 'ok' };
}
async getMarriageProposals(hashedUserId) {
const user = await this.getFalukantUserByHashedId(hashedUserId);
const character = await FalukantCharacter.findOne({ where: { userId: user.id } });
if (!character) {
throw new Error('Character not found for this user');
}
const midnight = new Date();
midnight.setHours(0, 0, 0, 0);
await MarriageProposal.destroy({
where: {
[Op.or]: [
{ requesterCharacterId: character.id },
{ proposedCharacterId: character.id },
],
createdAt: {
[Op.lt]: midnight,
},
},
});
let proposals = await MarriageProposal.findAll({
where: {
[Op.or]: [
{ requesterCharacterId: character.id },
{ proposedCharacterId: character.id },
],
},
});
if (proposals.length === 0) {
const proposalCount = Math.floor(Math.random() * 4) + 3; // 36
const thirteenDaysAgo = new Date(Date.now() - 13 * 24 * 60 * 60 * 1000);
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++) {
const partner = possiblePartners[i % possiblePartners.length];
const createdProposal = await MarriageProposal.create({
requesterCharacterId: character.id,
proposedCharacterId: partner.id,
courtingProgress: 0,
});
newProposals.push(createdProposal);
}
proposals = newProposals;
}
return proposals;
}
}
export default new FalukantService();