All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 2m59s
3789 lines
148 KiB
Rust
3789 lines
148 KiB
Rust
// Centralized SQL strings for workers.
|
||
|
||
pub const QUERY_UPDATE_MONEY: &str = r#"
|
||
SELECT falukant_data.update_money($1, $2, $3);
|
||
"#;
|
||
|
||
/// Insert in money_history für UI/Verlauf; Betrag als Parameter für zuverlässige Speicherung.
|
||
pub const QUERY_INSERT_MONEY_HISTORY: &str = r#"
|
||
INSERT INTO falukant_log.money_history (user_id, change, action, created_at) VALUES ($1, $2::numeric, $3, NOW())
|
||
"#;
|
||
|
||
pub const QUERY_GET_MONEY: &str = r#"
|
||
SELECT money FROM falukant_data.falukant_user WHERE id = $1;
|
||
"#;
|
||
|
||
|
||
pub const QUERY_GET_RANDOM_USER: &str = r#"
|
||
SELECT id FROM falukant_data.falukant_user ORDER BY RANDOM() LIMIT 1;
|
||
"#;
|
||
|
||
/// Nur NPC-Kleinkinder (user_id IS NULL); Spieler-Charaktere sind von plötzlichem Kindstod ausgenommen.
|
||
pub const QUERY_GET_RANDOM_INFANT: &str = r#"
|
||
SELECT c.id AS character_id, c.user_id, CURRENT_DATE - c.birthdate::date AS age_days
|
||
FROM falukant_data."character" c
|
||
WHERE c.user_id IS NULL AND c.health > 0 AND CURRENT_DATE - c.birthdate::date <= 730
|
||
ORDER BY RANDOM() LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_RANDOM_CITY: &str = r#"
|
||
SELECT r.id AS region_id FROM falukant_data.region r JOIN falukant_type.region tr ON r.region_type_id = tr.id WHERE tr.label_tr = 'city' ORDER BY RANDOM() LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_AFFECTED_USERS: &str = r#"
|
||
SELECT DISTINCT b.falukant_user_id AS user_id FROM falukant_data.branch b WHERE b.region_id = $1 AND b.falukant_user_id IS NOT NULL;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_WEATHER: &str = r#"
|
||
WITH all_regions AS (
|
||
SELECT DISTINCT r.id AS region_id FROM falukant_data.region r JOIN falukant_type.region tr ON r.region_type_id = tr.id WHERE tr.label_tr = 'city'
|
||
)
|
||
INSERT INTO falukant_data.weather (region_id, weather_type_id)
|
||
SELECT ar.region_id, (SELECT wt.id FROM falukant_type.weather wt ORDER BY random() + ar.region_id * 0 LIMIT 1) FROM all_regions ar
|
||
ON CONFLICT (region_id) DO UPDATE SET weather_type_id = EXCLUDED.weather_type_id;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_NOTIFICATION: &str = r#"
|
||
INSERT INTO falukant_log.notification (user_id, tr, shown, created_at, updated_at)
|
||
VALUES ($1, $2, FALSE, NOW(), NOW());
|
||
"#;
|
||
|
||
// Product pricing (nur sell_cost; für Produktions-Stückkosten siehe QUERY_GET_PRODUCT_CATEGORY_AND_USER_CERTIFICATE)
|
||
#[allow(dead_code)]
|
||
pub const QUERY_GET_PRODUCT_COST: &str = r#"
|
||
SELECT sell_cost FROM falukant_type.product WHERE id = $1;
|
||
"#;
|
||
|
||
/// Produktklasse + Spieler-Zertifikat für Stückkosten (kein „teurer wegen höherem Zertifikat“).
|
||
pub const QUERY_GET_PRODUCT_CATEGORY_AND_USER_CERTIFICATE: &str = r#"
|
||
SELECT COALESCE(p.category, 1)::int AS category,
|
||
COALESCE(u.certificate, 1)::int AS certificate
|
||
FROM falukant_type.product p
|
||
CROSS JOIN falukant_data.falukant_user u
|
||
WHERE p.id = $1::int
|
||
AND u.id = $2::int;
|
||
"#;
|
||
|
||
pub const QUERY_GET_DIRECTORS: &str = r#"
|
||
SELECT d.may_produce, d.may_sell, d.may_start_transport, b.id AS branch_id, fu.id AS falukantUserId, d.id
|
||
FROM falukant_data.director d
|
||
JOIN falukant_data.falukant_user fu ON fu.id = d.employer_user_id
|
||
JOIN falukant_data.character c ON c.id = d.director_character_id
|
||
JOIN falukant_data.branch b ON b.region_id = c.region_id AND b.falukant_user_id = fu.id
|
||
WHERE current_time BETWEEN '08:00:00' AND '17:00:00';
|
||
"#;
|
||
|
||
/// Bester Produktstart: Mit **mindestens einem** `falukant_data.vehicle` pro User — bester `worth_percent`
|
||
/// je Produkt über alle Filialregionen; **ohne** Fahrzeug nur noch **lokaler** Markt (Region der Direktor-Filiale),
|
||
/// sonst wäre der beste Fern-Preis nicht erreichbar.
|
||
pub const QUERY_GET_BEST_PRODUCTION: &str = r#"
|
||
SELECT fdu.id falukant_user_id, CAST(fdu.money AS text) AS money, fdu.certificate, ftp.id product_id, ftp.label_tr,
|
||
COALESCE(ftp.category, 1)::int AS product_category, fdb.region_id,
|
||
(SELECT SUM(quantity) FROM falukant_data.stock fds WHERE fds.branch_id = fdb.id) AS stock_size,
|
||
COALESCE((SELECT SUM(COALESCE(fdi.quantity, 0)) FROM falukant_data.stock fds JOIN falukant_data.inventory fdi ON fdi.stock_id = fds.id WHERE fds.branch_id = fdb.id), 0) AS used_in_stock,
|
||
( (ftp.sell_cost * (
|
||
CASE
|
||
WHEN EXISTS (
|
||
SELECT 1 FROM falukant_data.vehicle v
|
||
WHERE v.falukant_user_id = fdu.id
|
||
)
|
||
THEN bw.max_worth_pct
|
||
ELSE COALESCE(fdtpw_local.worth_percent::float8, 0.0)
|
||
END / 100.0
|
||
)) / NULLIF(ftp.production_time::float8, 0.0) ) AS worth,
|
||
fdb.id AS branch_id, (SELECT COUNT(id) FROM falukant_data.production WHERE branch_id = fdb.id) AS running_productions,
|
||
COALESCE((SELECT SUM(COALESCE(fdp.quantity, 0)) quantity FROM falukant_data.production fdp WHERE fdp.branch_id = fdb.id), 0) AS running_productions_quantity
|
||
FROM falukant_data.director fdd
|
||
JOIN falukant_data.character fdc ON fdc.id = fdd.director_character_id
|
||
JOIN falukant_data.falukant_user fdu ON fdd.employer_user_id = fdu.id
|
||
JOIN falukant_data.character user_character ON user_character.user_id = fdu.id
|
||
JOIN falukant_data.branch fdb ON fdb.falukant_user_id = fdu.id AND fdb.region_id = fdc.region_id
|
||
JOIN (
|
||
SELECT tpw.product_id,
|
||
MAX(tpw.worth_percent)::float8 AS max_worth_pct
|
||
FROM falukant_data.town_product_worth tpw
|
||
WHERE tpw.region_id IN (
|
||
SELECT DISTINCT br.region_id
|
||
FROM falukant_data.branch br
|
||
WHERE br.falukant_user_id = (SELECT d0.employer_user_id FROM falukant_data.director d0 WHERE d0.id = $1::int)
|
||
)
|
||
GROUP BY tpw.product_id
|
||
) bw ON TRUE
|
||
JOIN falukant_type.product ftp ON ftp.id = bw.product_id AND ftp.category <= fdu.certificate
|
||
LEFT JOIN falukant_data.town_product_worth fdtpw_local
|
||
ON fdtpw_local.region_id = fdb.region_id
|
||
AND fdtpw_local.product_id = ftp.id
|
||
JOIN falukant_data.knowledge fdk_character ON fdk_character.product_id = ftp.id AND fdk_character.character_id = user_character.id
|
||
JOIN falukant_data.knowledge fdk_director ON fdk_director.product_id = ftp.id AND fdk_director.character_id = fdd.director_character_id
|
||
WHERE fdd.id = $1 AND fdb.id = $2 ORDER BY worth DESC LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_PRODUCTION: &str = r#"
|
||
INSERT INTO falukant_data.production (branch_id, product_id, quantity, weather_type_id) VALUES ($1, $2, $3, (SELECT weather_type_id FROM falukant_data.weather WHERE region_id = $4));
|
||
"#;
|
||
|
||
// Character creation related queries (missing from earlier extraction)
|
||
pub const QUERY_IS_PREVIOUS_DAY_CHARACTER_CREATED: &str = r#"
|
||
SELECT created_at
|
||
FROM falukant_data."character"
|
||
WHERE user_id IS NULL
|
||
AND created_at::date = CURRENT_DATE
|
||
ORDER BY created_at DESC
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_TOWN_REGION_IDS: &str = r#"
|
||
SELECT fdr.id
|
||
FROM falukant_data.region fdr
|
||
JOIN falukant_type.region ftr ON fdr.region_type_id = ftr.id
|
||
WHERE ftr.label_tr = 'city';
|
||
"#;
|
||
|
||
pub const QUERY_LOAD_FIRST_NAMES: &str = r#"
|
||
SELECT id, gender
|
||
FROM falukant_predefine.firstname;
|
||
"#;
|
||
|
||
pub const QUERY_LOAD_LAST_NAMES: &str = r#"
|
||
SELECT id
|
||
FROM falukant_predefine.lastname;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_CHARACTER: &str = r#"
|
||
INSERT INTO falukant_data.character(
|
||
user_id,
|
||
region_id,
|
||
first_name,
|
||
last_name,
|
||
birthdate,
|
||
gender,
|
||
created_at,
|
||
updated_at,
|
||
title_of_nobility
|
||
) VALUES (
|
||
NULL,
|
||
$1,
|
||
$2,
|
||
$3,
|
||
NOW(),
|
||
$4,
|
||
NOW(),
|
||
NOW(),
|
||
$5
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_GET_ELIGIBLE_NPC_FOR_DEATH: &str = r#"
|
||
WITH aged AS (
|
||
SELECT
|
||
c.id,
|
||
(current_date - c.birthdate::date) AS age,
|
||
c.user_id
|
||
FROM
|
||
falukant_data.character c
|
||
WHERE
|
||
c.user_id IS NULL
|
||
AND (current_date - c.birthdate::date) > 60
|
||
),
|
||
always_sel AS (
|
||
SELECT *
|
||
FROM aged
|
||
WHERE age > 85
|
||
),
|
||
random_sel AS (
|
||
SELECT *
|
||
FROM aged
|
||
WHERE age <= 85
|
||
ORDER BY random()
|
||
LIMIT 10
|
||
)
|
||
SELECT *
|
||
FROM always_sel
|
||
UNION ALL
|
||
SELECT *
|
||
FROM random_sel;
|
||
"#;
|
||
|
||
pub const QUERY_MARK_CHARACTER_DECEASED: &str = r#"
|
||
DELETE FROM falukant_data.character
|
||
WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_BRANCH_CAPACITY: &str = r#"
|
||
SELECT (SELECT SUM(quantity) FROM falukant_data.stock fds WHERE fds.branch_id = $1) AS stock_size,
|
||
COALESCE((SELECT SUM(COALESCE(fdi.quantity, 0)) FROM falukant_data.stock fds JOIN falukant_data.inventory fdi ON fdi.stock_id = fds.id WHERE fds.branch_id = $1), 0) AS used_in_stock,
|
||
(SELECT COUNT(id) FROM falukant_data.production WHERE branch_id = $1) AS running_productions,
|
||
COALESCE((SELECT SUM(COALESCE(fdp.quantity, 0)) quantity FROM falukant_data.production fdp WHERE fdp.branch_id = $1), 0) AS running_productions_quantity;
|
||
"#;
|
||
|
||
pub const QUERY_GET_INVENTORY: &str = r#"
|
||
SELECT i.id, i.product_id, i.quantity, i.quality, p.sell_cost, fu.id AS user_id, b.region_id, b.id AS branch_id, COALESCE(tpw.worth_percent, 100.0) AS worth_percent
|
||
FROM falukant_data.inventory i
|
||
JOIN falukant_data.stock s ON s.id = i.stock_id
|
||
JOIN falukant_data.branch b ON b.id = s.branch_id
|
||
JOIN falukant_data.falukant_user fu ON fu.id = b.falukant_user_id
|
||
JOIN falukant_data.director d ON d.employer_user_id = fu.id
|
||
JOIN falukant_type.product p ON p.id = i.product_id
|
||
LEFT JOIN falukant_data.town_product_worth tpw ON tpw.region_id = b.region_id AND tpw.product_id = i.product_id
|
||
WHERE d.id = $1 AND b.id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_REMOVE_INVENTORY: &str = r#"
|
||
DELETE FROM falukant_data.inventory WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_ADD_SELL_LOG: &str = r#"
|
||
INSERT INTO falukant_log.sell (region_id, product_id, quantity, seller_id) VALUES ($1, $2, $3, $4)
|
||
ON CONFLICT (region_id, product_id, seller_id) DO UPDATE SET quantity = falukant_log.sell.quantity + EXCLUDED.quantity;
|
||
"#;
|
||
pub const QUERY_GET_ARRIVED_TRANSPORTS: &str = r#"
|
||
SELECT
|
||
t.id,
|
||
t.product_id,
|
||
t.size,
|
||
t.vehicle_id,
|
||
t.source_region_id,
|
||
t.target_region_id,
|
||
b_target.id AS target_branch_id,
|
||
b_source.id AS source_branch_id,
|
||
rd.distance AS distance,
|
||
v.falukant_user_id AS user_id
|
||
FROM falukant_data.transport AS t
|
||
JOIN falukant_data.vehicle AS v ON v.id = t.vehicle_id
|
||
JOIN falukant_type.vehicle AS vt ON vt.id = v.vehicle_type_id
|
||
JOIN falukant_data.region_distance AS rd ON ((rd.source_region_id = t.source_region_id AND rd.target_region_id = t.target_region_id) OR (rd.source_region_id = t.target_region_id AND rd.target_region_id = t.source_region_id)) AND (rd.transport_mode = vt.transport_mode OR rd.transport_mode IS NULL)
|
||
LEFT JOIN falukant_data.branch AS b_target ON b_target.region_id = t.target_region_id AND b_target.falukant_user_id = v.falukant_user_id
|
||
LEFT JOIN falukant_data.branch AS b_source ON b_source.region_id = t.source_region_id AND b_source.falukant_user_id = v.falukant_user_id
|
||
WHERE vt.speed > 0 AND t.created_at + (rd.distance / vt.speed::double precision) * INTERVAL '1 minute' <= NOW();
|
||
"#;
|
||
|
||
pub const QUERY_GET_AVAILABLE_STOCKS: &str = r#"
|
||
SELECT
|
||
stock.id,
|
||
stock.quantity AS total_capacity,
|
||
(
|
||
SELECT COALESCE(SUM(inventory.quantity), 0)
|
||
FROM falukant_data.inventory
|
||
WHERE inventory.stock_id = stock.id
|
||
) AS filled
|
||
FROM falukant_data.stock stock
|
||
JOIN falukant_data.branch branch
|
||
ON stock.branch_id = branch.id
|
||
WHERE branch.id = $1
|
||
ORDER BY total_capacity DESC;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_INVENTORY: &str = r#"
|
||
INSERT INTO falukant_data.inventory (stock_id, product_id, quantity, quality, produced_at)
|
||
VALUES ($1, $2, $3, $4, NOW())
|
||
RETURNING id;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_INVENTORY_BY_STOCK_PRODUCT: &str = r#"
|
||
UPDATE falukant_data.inventory
|
||
SET quantity = quantity + $3,
|
||
quality = LEAST(
|
||
100,
|
||
ROUND(
|
||
((quantity * quality) + ($3 * $4))::numeric / NULLIF(quantity + $3, 0)
|
||
)
|
||
)
|
||
WHERE stock_id = $1 AND product_id = $2
|
||
RETURNING id;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_VEHICLE_AFTER_TRANSPORT: &str = r#"
|
||
UPDATE falukant_data.vehicle SET region_id = $2, condition = GREATEST(0, condition - $3::int), available_from = NOW(), updated_at = NOW() WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_TRANSPORT: &str = r#"
|
||
DELETE FROM falukant_data.transport WHERE id = $1;
|
||
"#;
|
||
|
||
#[allow(dead_code)]
|
||
pub const QUERY_ADD_TRANSPORT_WAITING_NOTIFICATION: &str = r#"
|
||
INSERT INTO falukant_log.notification (user_id, tr, shown, created_at, updated_at)
|
||
VALUES ((SELECT c.user_id FROM falukant_data.character c WHERE c.user_id = $1 LIMIT 1), $2, FALSE, NOW(), NOW());
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_TRANSPORT_SIZE: &str = r#"
|
||
UPDATE falukant_data.transport
|
||
SET size = $2,
|
||
updated_at = NOW()
|
||
WHERE id = $1;
|
||
"#;
|
||
|
||
// --- Falukant: Transportüberfälle (docs/FALUKANT_TRANSPORT_RAID_DAEMON.md) ---
|
||
pub const QUERY_RAID_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'transport'
|
||
AND column_name = 'guard_count'
|
||
) AS ready;
|
||
"#;
|
||
|
||
pub const QUERY_RAID_ACTIVE_UNDERGROUND: &str = r#"
|
||
SELECT u.id,
|
||
u.performer_id,
|
||
COALESCE(u.parameters::text, '{}') AS parameters
|
||
FROM falukant_data.underground u
|
||
JOIN falukant_type.underground t ON t.tr = u.underground_type_id
|
||
WHERE u.result IS NULL
|
||
AND t.tr = 'raid_transport'
|
||
ORDER BY u.created_at ASC
|
||
LIMIT 100;
|
||
"#;
|
||
|
||
pub const QUERY_RAID_REGION_ALLOWED: &str = r#"
|
||
SELECT r.id
|
||
FROM falukant_data.region r
|
||
JOIN falukant_type.region rt ON rt.id = r.region_type_id
|
||
WHERE r.id = $1::int
|
||
AND rt.id IN (4, 5)
|
||
AND COALESCE(rt.label_tr, '') <> 'town';
|
||
"#;
|
||
|
||
pub const QUERY_RAID_FALUKANT_USER_FOR_CHARACTER: &str = r#"
|
||
SELECT fu.id AS falukant_user_id, fu.user_id AS app_user_id
|
||
FROM falukant_data.character c
|
||
JOIN falukant_data.falukant_user fu ON fu.user_id = c.user_id
|
||
WHERE c.id = $1::int
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
/// Aktive (noch unterwegs) Transporte mit Fracht, Route berührt Region, nicht vom Auftraggeber.
|
||
pub const QUERY_RAID_CANDIDATE_TRANSPORTS: &str = r#"
|
||
SELECT
|
||
t.id AS transport_id,
|
||
t.size AS transport_size,
|
||
t.product_id,
|
||
COALESCE(t.guard_count, 0)::int AS guard_count,
|
||
v.falukant_user_id AS victim_falukant_user_id,
|
||
vt.capacity AS vehicle_capacity
|
||
FROM falukant_data.transport t
|
||
JOIN falukant_data.vehicle v ON v.id = t.vehicle_id
|
||
JOIN falukant_type.vehicle vt ON vt.id = v.vehicle_type_id
|
||
JOIN falukant_data.region_distance rd
|
||
ON (
|
||
(rd.source_region_id = t.source_region_id AND rd.target_region_id = t.target_region_id)
|
||
OR (rd.source_region_id = t.target_region_id AND rd.target_region_id = t.source_region_id)
|
||
)
|
||
AND (rd.transport_mode = vt.transport_mode OR rd.transport_mode IS NULL)
|
||
WHERE t.product_id IS NOT NULL
|
||
AND t.size > 0
|
||
AND vt.speed > 0
|
||
AND t.created_at + (rd.distance / vt.speed::double precision) * INTERVAL '1 minute' > NOW()
|
||
AND (t.source_region_id = $1::int OR t.target_region_id = $1::int)
|
||
AND v.falukant_user_id <> $2::int
|
||
ORDER BY RANDOM()
|
||
LIMIT 20;
|
||
"#;
|
||
|
||
/// Bevorzugt eine Niederlassung in der Überfallregion, sonst kleinste branch_id.
|
||
pub const QUERY_RAID_NEAREST_BRANCH_FOR_USER: &str = r#"
|
||
SELECT b.id AS branch_id, b.region_id
|
||
FROM falukant_data.branch b
|
||
WHERE b.falukant_user_id = $2::int
|
||
ORDER BY CASE WHEN b.region_id = $1::int THEN 0 ELSE 1 END, b.id ASC
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_RAID_APP_USER_FOR_FALUKANT: &str = r#"
|
||
SELECT fu.user_id
|
||
FROM falukant_data.falukant_user fu
|
||
WHERE fu.id = $1::int
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_RAID_UPDATE_UNDERGROUND_RESULT: &str = r#"
|
||
UPDATE falukant_data.underground
|
||
SET result = $2::jsonb,
|
||
updated_at = NOW()
|
||
WHERE id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_RAID_SUBTRACT_REP_BY_USER: &str = r#"
|
||
UPDATE falukant_data.character c
|
||
SET reputation = GREATEST(0::numeric, COALESCE(c.reputation, 50::numeric) - $2::numeric),
|
||
updated_at = NOW()
|
||
FROM falukant_data.falukant_user fu
|
||
WHERE fu.id = $1::int
|
||
AND fu.user_id = c.user_id
|
||
AND c.health > 0;
|
||
"#;
|
||
|
||
pub const QUERY_GET_REGION_WORTH_FOR_PRODUCT: &str = r#"
|
||
SELECT tpw.region_id, tpw.product_id, tpw.worth_percent FROM falukant_data.town_product_worth tpw JOIN falukant_data.branch b ON b.region_id = tpw.region_id WHERE b.falukant_user_id = $1 AND tpw.product_id = $2;
|
||
"#;
|
||
|
||
// Political offices and cumulative tax
|
||
pub const QUERY_GET_USER_OFFICES: &str = r#"
|
||
SELECT po.id AS office_id, pot.name AS office_name, po.region_id, rt.label_tr AS region_type
|
||
FROM falukant_data.political_office po
|
||
JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id
|
||
JOIN falukant_data.region r ON r.id = po.region_id
|
||
JOIN falukant_type.region rt ON rt.id = r.region_type_id
|
||
JOIN falukant_data.character ch ON ch.id = po.character_id
|
||
WHERE ch.user_id = $1
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) > NOW();
|
||
"#;
|
||
|
||
pub const QUERY_CUMULATIVE_TAX_NO_EXEMPT: &str = r#"
|
||
WITH RECURSIVE ancestors AS (
|
||
SELECT id, parent_id, COALESCE(tax_percent,0.0) AS tax_percent FROM falukant_data.region WHERE id = $1
|
||
UNION ALL
|
||
SELECT r.id, r.parent_id, COALESCE(r.tax_percent,0.0) FROM falukant_data.region r JOIN ancestors a ON r.id = a.parent_id
|
||
)
|
||
SELECT COALESCE(SUM(tax_percent),0.0) AS total_percent FROM ancestors;
|
||
"#;
|
||
|
||
pub const QUERY_CUMULATIVE_TAX_WITH_EXEMPT: &str = r#"
|
||
WITH RECURSIVE ancestors AS (
|
||
SELECT r.id, r.parent_id, CASE WHEN rt.label_tr = ANY($2::text[]) THEN 0.0 ELSE COALESCE(r.tax_percent,0.0) END AS tax_percent
|
||
FROM falukant_data.region r JOIN falukant_type.region rt ON rt.id = r.region_type_id WHERE r.id = $1
|
||
UNION ALL
|
||
SELECT r.id, r.parent_id, CASE WHEN rt.label_tr = ANY($2::text[]) THEN 0.0 ELSE COALESCE(r.tax_percent,0.0) END
|
||
FROM falukant_data.region r JOIN falukant_type.region rt ON rt.id = r.region_type_id JOIN ancestors a ON r.id = a.parent_id
|
||
)
|
||
SELECT COALESCE(SUM(tax_percent),0.0) AS total_percent FROM ancestors;
|
||
"#;
|
||
|
||
pub const QUERY_GET_TRANSPORT_VEHICLES_FOR_ROUTE: &str = r#"
|
||
SELECT v.id AS vehicle_id, vt.capacity AS capacity
|
||
FROM falukant_data.vehicle v
|
||
JOIN falukant_type.vehicle vt ON vt.id = v.vehicle_type_id
|
||
JOIN falukant_data.region_distance rd ON ((rd.source_region_id = v.region_id AND rd.target_region_id = $3) OR (rd.source_region_id = $3 AND rd.target_region_id = v.region_id)) AND (rd.transport_mode = vt.transport_mode OR rd.transport_mode IS NULL)
|
||
WHERE v.falukant_user_id = $1 AND v.region_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_TRANSPORT: &str = r#"
|
||
INSERT INTO falukant_data.transport (source_region_id, target_region_id, product_id, size, vehicle_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, NOW(), NOW());
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_EMPTY_TRANSPORT: &str = r#"
|
||
INSERT INTO falukant_data.transport (source_region_id, target_region_id, product_id, size, vehicle_id, created_at, updated_at) VALUES ($1, $2, NULL, 0, $3, NOW(), NOW());
|
||
"#;
|
||
|
||
pub const QUERY_GET_USER_BRANCHES: &str = r#"
|
||
SELECT DISTINCT b.region_id, b.id AS branch_id FROM falukant_data.branch b WHERE b.falukant_user_id = $1 AND b.region_id != $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_FREE_VEHICLES_IN_REGION: &str = r#"
|
||
SELECT v.id AS vehicle_id, vt.capacity AS capacity FROM falukant_data.vehicle v JOIN falukant_type.vehicle vt ON vt.id = v.vehicle_type_id WHERE v.falukant_user_id = $1 AND v.region_id = $2 AND v.id NOT IN (SELECT DISTINCT t.vehicle_id FROM falukant_data.transport t WHERE t.vehicle_id IS NOT NULL);
|
||
"#;
|
||
|
||
pub const QUERY_GET_SALARY_TO_PAY: &str = r#"
|
||
SELECT d.id, d.employer_user_id, d.income FROM falukant_data.director d WHERE DATE(d.last_salary_payout) < DATE(NOW());
|
||
"#;
|
||
|
||
pub const QUERY_SET_SALARY_PAYED: &str = r#"
|
||
UPDATE falukant_data.director SET last_salary_payout = NOW() WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_SATISFACTION: &str = r#"
|
||
WITH new_sats AS (
|
||
SELECT d.id, ROUND(d.income::numeric / (c.title_of_nobility * POWER(1.231, AVG(k.knowledge) / 1.5)) * 100) AS new_satisfaction
|
||
FROM falukant_data.director d
|
||
JOIN falukant_data.knowledge k ON d.director_character_id = k.character_id
|
||
JOIN falukant_data.character c ON c.id = d.director_character_id
|
||
GROUP BY d.id, c.title_of_nobility, d.income
|
||
)
|
||
UPDATE falukant_data.director dir SET satisfaction = ns.new_satisfaction FROM new_sats ns WHERE dir.id = ns.id AND dir.satisfaction IS DISTINCT FROM ns.new_satisfaction RETURNING dir.employer_user_id;
|
||
"#;
|
||
|
||
pub const QUERY_GET_DIRECTOR_USER: &str = r#"
|
||
SELECT fu.id AS falukant_user_id FROM falukant_data.director d JOIN falukant_data.falukant_user fu ON fu.id = d.employer_user_id WHERE d.id = $1 LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_COUNT_VEHICLES_IN_BRANCH_REGION: &str = r#"
|
||
SELECT COUNT(v.id) AS cnt FROM falukant_data.vehicle v WHERE v.falukant_user_id = $1 AND v.region_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_COUNT_VEHICLES_IN_REGION: &str = r#"
|
||
SELECT COUNT(v.id) AS cnt FROM falukant_data.vehicle v WHERE v.falukant_user_id = $1 AND v.region_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_CHECK_ROUTE: &str = r#"
|
||
SELECT 1 FROM falukant_data.region_distance rd WHERE (rd.source_region_id = $1 AND rd.target_region_id = $2) OR (rd.source_region_id = $2 AND rd.target_region_id = $1) LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_BRANCH_REGION: &str = r#"
|
||
SELECT region_id FROM falukant_data.branch WHERE id = $1 LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_AVERAGE_WORTH: &str = r#"
|
||
SELECT AVG(tpw.worth_percent) AS avg_worth FROM falukant_data.town_product_worth tpw WHERE tpw.product_id = $1 AND tpw.region_id IN (SELECT region_id FROM falukant_data.branch WHERE falukant_user_id = $2);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_INVENTORY_QTY: &str = r#"
|
||
UPDATE falukant_data.inventory SET quantity = $1 WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_USER_STOCKS: &str = r#"
|
||
SELECT s.id AS stock_id, s.quantity AS current_capacity
|
||
FROM falukant_data.stock s
|
||
JOIN falukant_data.branch b ON s.branch_id = b.id
|
||
WHERE b.falukant_user_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_STOCK_CAPACITY: &str = r#"
|
||
UPDATE falukant_data.stock
|
||
SET quantity = GREATEST(1, ROUND(quantity * (1 + $1 / 100.0)))
|
||
WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_REGION_STOCKS: &str = r#"
|
||
SELECT s.id AS stock_id, s.quantity AS current_capacity
|
||
FROM falukant_data.stock s
|
||
JOIN falukant_data.branch b ON s.branch_id = b.id
|
||
WHERE b.region_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_STOCK_CAPACITY_REGIONAL: &str = r#"
|
||
UPDATE falukant_data.stock
|
||
SET quantity = GREATEST(1, ROUND(quantity * (1 + $1 / 100.0)))
|
||
WHERE id = $2;
|
||
"#;
|
||
|
||
// Stockage manager specific queries
|
||
pub const QUERY_GET_TOWNS: &str = r#"
|
||
SELECT fdr.id
|
||
FROM falukant_data.region fdr
|
||
JOIN falukant_type.region ftr
|
||
ON ftr.id = fdr.region_type_id
|
||
WHERE ftr.label_tr = 'city';
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_STOCK: &str = r#"
|
||
INSERT INTO falukant_data.buyable_stock (region_id, stock_type_id, quantity)
|
||
SELECT
|
||
$1 AS region_id,
|
||
s.id AS stock_type_id,
|
||
GREATEST(1, ROUND(RANDOM() * 5 * COUNT(br.id))) AS quantity
|
||
FROM falukant_data.branch AS br
|
||
CROSS JOIN falukant_type.stock AS s
|
||
WHERE br.region_id = $1
|
||
GROUP BY s.id
|
||
ORDER BY RANDOM()
|
||
LIMIT GREATEST(
|
||
ROUND(RANDOM() * (SELECT COUNT(id) FROM falukant_type.stock)),
|
||
1
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_CLEANUP_STOCK: &str = r#"
|
||
DELETE FROM falukant_data.buyable_stock
|
||
WHERE quantity <= 0;
|
||
"#;
|
||
|
||
pub const QUERY_GET_REGION_USERS: &str = r#"
|
||
SELECT c.user_id
|
||
FROM falukant_data.character c
|
||
WHERE c.region_id = $1
|
||
AND c.user_id IS NOT NULL;
|
||
"#;
|
||
|
||
pub const QUERY_GET_REGION_HOUSES: &str = r#"
|
||
SELECT uh.id AS house_id, uh.roof_condition, uh.floor_condition, uh.wall_condition, uh.window_condition
|
||
FROM falukant_data.user_house uh
|
||
JOIN falukant_data.character c ON c.user_id = uh.user_id
|
||
WHERE c.region_id = $1
|
||
AND uh.house_type_id NOT IN (
|
||
SELECT id FROM falukant_type.house h WHERE h.label_tr = 'under_bridge'
|
||
);
|
||
"#;
|
||
|
||
// House worker queries
|
||
pub const QUERY_GET_NEW_HOUSE_DATA: &str = r#"
|
||
SELECT
|
||
h.id AS house_id
|
||
FROM
|
||
falukant_type.house AS h
|
||
WHERE
|
||
random() < 0.01
|
||
AND label_tr <> 'under_bridge';
|
||
"#;
|
||
|
||
pub const QUERY_ADD_NEW_BUYABLE_HOUSE: &str = r#"
|
||
INSERT INTO falukant_data.buyable_house (house_type_id)
|
||
VALUES ($1);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_BUYABLE_HOUSE_STATE: &str = r#"
|
||
UPDATE falukant_data.buyable_house
|
||
SET roof_condition = ROUND(roof_condition - random() * (3 + 0 * id)),
|
||
floor_condition = ROUND(floor_condition - random() * (3 + 0 * id)),
|
||
wall_condition = ROUND(wall_condition - random() * (3 + 0 * id)),
|
||
window_condition = ROUND(window_condition - random() * (3 + 0 * id));
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_USER_HOUSE_STATE: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET roof_condition = ROUND(roof_condition - random() * (3 + 0 * id)),
|
||
floor_condition = ROUND(floor_condition - random() * (3 + 0 * id)),
|
||
wall_condition = ROUND(wall_condition - random() * (3 + 0 * id)),
|
||
window_condition = ROUND(window_condition - random() * (3 + 0 * id))
|
||
WHERE house_type_id NOT IN (
|
||
SELECT id
|
||
FROM falukant_type.house h
|
||
WHERE h.label_tr = 'under_bridge'
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_HOUSE_QUALITY: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET roof_condition = GREATEST(0, LEAST(100, roof_condition + $1)),
|
||
floor_condition = GREATEST(0, LEAST(100, floor_condition + $1)),
|
||
wall_condition = GREATEST(0, LEAST(100, wall_condition + $1)),
|
||
window_condition = GREATEST(0, LEAST(100, window_condition + $1))
|
||
WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_CHANGE_WEATHER: &str = r#"
|
||
UPDATE falukant_data.weather
|
||
SET weather_type_id = (
|
||
SELECT id FROM falukant_type.weather ORDER BY RANDOM() LIMIT 1
|
||
)
|
||
WHERE region_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_RANDOM_CHARACTER: &str = r#"
|
||
SELECT id, health
|
||
FROM falukant_data."character"
|
||
WHERE user_id = $1 AND health > 0
|
||
ORDER BY RANDOM() LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_HEALTH: &str = r#"
|
||
UPDATE falukant_data."character" SET health = $1 WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_REGION_CHARACTERS: &str = r#"
|
||
SELECT id, health, user_id FROM falukant_data."character" WHERE region_id = $1 AND health > 0;
|
||
"#;
|
||
|
||
/// Anzeige für Todes-Logs (`falukant_log.notification.tr` als JSON): Name, Wohnort, Alter, Verknüpfungen.
|
||
/// `region_id` = Ansässigkeit; `region_label` aus `falukant_data.region.name`, falls vorhanden, sonst `region_id` als Text.
|
||
pub const QUERY_DEATH_LOG_CHARACTER_BASE: &str = r#"
|
||
SELECT
|
||
c.id AS character_id,
|
||
COALESCE(TRIM(BOTH FROM COALESCE(fn.label::text, '') || ' ' || COALESCE(ln.label::text, '')), c.id::text) AS display_name,
|
||
c.region_id,
|
||
COALESCE(NULLIF(TRIM(dr.name::text), ''), c.region_id::text) AS region_label,
|
||
GREATEST(0, FLOOR((CURRENT_DATE - c.birthdate::date) / 365.25))::int AS age_years
|
||
FROM falukant_data.character c
|
||
LEFT JOIN falukant_predefine.firstname fn ON fn.id = c.first_name
|
||
LEFT JOIN falukant_predefine.lastname ln ON ln.id = c.last_name
|
||
LEFT JOIN falukant_data.region dr ON dr.id = c.region_id
|
||
WHERE c.id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_DEATH_LOG_SPOUSE_DISPLAY_NAMES: &str = r#"
|
||
SELECT COALESCE(TRIM(BOTH FROM COALESCE(fn.label::text, '') || ' ' || COALESCE(ln.label::text, '')), o.id::text) AS display_name
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
JOIN falukant_data.character o ON o.id = CASE WHEN r.character1_id = $1::int THEN r.character2_id ELSE r.character1_id END
|
||
LEFT JOIN falukant_predefine.firstname fn ON fn.id = o.first_name
|
||
LEFT JOIN falukant_predefine.lastname ln ON ln.id = o.last_name
|
||
WHERE r.character1_id = $1::int OR r.character2_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_DEATH_LOG_CHILD_DISPLAY_NAMES: &str = r#"
|
||
SELECT COALESCE(TRIM(BOTH FROM COALESCE(fn.label::text, '') || ' ' || COALESCE(ln.label::text, '')), ch.id::text) AS display_name
|
||
FROM falukant_data.child_relation cr
|
||
JOIN falukant_data.character ch ON ch.id = cr.child_character_id
|
||
LEFT JOIN falukant_predefine.firstname fn ON fn.id = ch.first_name
|
||
LEFT JOIN falukant_predefine.lastname ln ON ln.id = ch.last_name
|
||
WHERE cr.father_character_id = $1::int OR cr.mother_character_id = $1::int;
|
||
"#;
|
||
|
||
/// Kinder mit `birth_context = 'lover'` (unehelich / aus Liebschaft); Teilmenge von `QUERY_DEATH_LOG_CHILD_DISPLAY_NAMES`.
|
||
pub const QUERY_DEATH_LOG_CHILD_LOVER_BIRTH_DISPLAY_NAMES: &str = r#"
|
||
SELECT COALESCE(TRIM(BOTH FROM COALESCE(fn.label::text, '') || ' ' || COALESCE(ln.label::text, '')), ch.id::text) AS display_name
|
||
FROM falukant_data.child_relation cr
|
||
JOIN falukant_data.character ch ON ch.id = cr.child_character_id
|
||
LEFT JOIN falukant_predefine.firstname fn ON fn.id = ch.first_name
|
||
LEFT JOIN falukant_predefine.lastname ln ON ln.id = ch.last_name
|
||
WHERE (cr.father_character_id = $1::int OR cr.mother_character_id = $1::int)
|
||
AND cr.birth_context = 'lover';
|
||
"#;
|
||
|
||
/// Geliebte: Zeilen in `falukant_data.relationship`, Typ über `falukant_type.relationship` mit `tr = 'lover'`
|
||
/// (kein Filter auf `relationship_state.lover_role` — `secret_affair` / `lover` / `mistress_or_favorite` sind nur Ausprägungen).
|
||
/// „Aktiv“ wie Backend (`falukantService`): `relationship_state.active` darf nicht `false` sein (`IS NOT FALSE`).
|
||
pub const QUERY_DEATH_LOG_LOVER_DISPLAY_NAMES: &str = r#"
|
||
SELECT COALESCE(TRIM(BOTH FROM COALESCE(fn.label::text, '') || ' ' || COALESCE(ln.label::text, '')), o.id::text) AS display_name
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id AND (rs.active IS NOT FALSE)
|
||
JOIN falukant_data.character o ON o.id = CASE WHEN r.character1_id = $1::int THEN r.character2_id ELSE r.character1_id END
|
||
LEFT JOIN falukant_predefine.firstname fn ON fn.id = o.first_name
|
||
LEFT JOIN falukant_predefine.lastname ln ON ln.id = o.last_name
|
||
WHERE r.character1_id = $1::int OR r.character2_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_DIRECTOR: &str = r#"
|
||
DELETE FROM falukant_data.director WHERE director_character_id = $1 RETURNING employer_user_id;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_RELATIONSHIP: &str = r#"
|
||
WITH deleted AS (
|
||
DELETE FROM falukant_data.relationship
|
||
WHERE character1_id = $1 OR character2_id = $1
|
||
RETURNING CASE WHEN character1_id = $1 THEN character2_id ELSE character1_id END AS related_character_id, relationship_type_id
|
||
)
|
||
SELECT c.user_id AS related_user_id FROM deleted d JOIN falukant_data.character c ON c.id = d.related_character_id;
|
||
"#;
|
||
|
||
pub const QUERY_GET_USER_ID: &str = r#"
|
||
SELECT user_id FROM falukant_data.character WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_CHILD_RELATION: &str = r#"
|
||
WITH deleted AS (
|
||
DELETE FROM falukant_data.child_relation WHERE child_character_id = $1 RETURNING father_character_id, mother_character_id
|
||
)
|
||
SELECT cf.user_id AS father_user_id, cm.user_id AS mother_user_id FROM deleted d JOIN falukant_data.character cf ON cf.id = d.father_character_id JOIN falukant_data.character cm ON cm.id = d.mother_character_id;
|
||
"#;
|
||
|
||
/// Entfernt alle child_relation-Zeilen, in denen der Charakter Vater oder Mutter ist (nötig vor Charakter-Löschung).
|
||
pub const QUERY_DELETE_CHILD_RELATION_BY_PARENT: &str = r#"
|
||
DELETE FROM falukant_data.child_relation WHERE father_character_id = $1 OR mother_character_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_CHARACTER: &str = r#"
|
||
DELETE FROM falukant_data.character WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_HEIR: &str = r#"
|
||
SELECT child_character_id FROM falukant_data.child_relation WHERE father_character_id = $1 OR mother_character_id = $1 ORDER BY (is_heir IS TRUE) DESC, updated_at DESC LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_SET_CHARACTER_USER: &str = r#"
|
||
UPDATE falukant_data.character SET user_id = $1, updated_at = NOW() WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_CURRENT_MONEY: &str = r#"
|
||
SELECT money FROM falukant_data.falukant_user WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_HOUSE_VALUE: &str = r#"
|
||
SELECT COALESCE(SUM(h.cost), 0) AS sum FROM falukant_data.user_house AS uh JOIN falukant_type.house AS h ON uh.house_type_id = h.id WHERE uh.user_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_SETTLEMENT_VALUE: &str = r#"
|
||
SELECT COALESCE(SUM(b.base_cost), 0) AS sum FROM falukant_data.branch AS br JOIN falukant_type.branch AS b ON br.branch_type_id = b.id WHERE br.falukant_user_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_INVENTORY_VALUE: &str = r#"
|
||
SELECT COALESCE(SUM(i.quantity * p.sell_cost), 0) AS sum FROM falukant_data.inventory AS i JOIN falukant_type.product AS p ON i.product_id = p.id JOIN falukant_data.stock AS s ON i.stock_id = s.id JOIN falukant_data.branch AS br ON s.branch_id = br.id WHERE br.falukant_user_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_CREDIT_DEBT: &str = r#"
|
||
SELECT COALESCE(SUM(remaining_amount), 0) AS sum FROM falukant_data.credit WHERE falukant_user_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_COUNT_CHILDREN: &str = r#"
|
||
SELECT COUNT(*) AS cnt FROM falukant_data.child_relation WHERE (father_character_id = $1 OR mother_character_id = $1) AND child_character_id != $2;
|
||
"#;
|
||
|
||
/// Zählt Kinder eines Users (über alle Charaktere des Users als Elternteil). Ein Parameter: user_id.
|
||
pub const QUERY_COUNT_CHILDREN_BY_USER: &str = r#"
|
||
SELECT COUNT(DISTINCT cr.child_character_id) AS cnt
|
||
FROM falukant_data.child_relation cr
|
||
JOIN falukant_data.character parent ON (parent.id = cr.father_character_id OR parent.id = cr.mother_character_id)
|
||
WHERE parent.user_id = $1;
|
||
"#;
|
||
|
||
// user_character worker queries
|
||
pub const QUERY_GET_USERS_TO_UPDATE: &str = r#"
|
||
SELECT id, CURRENT_DATE - birthdate::date AS age, health
|
||
FROM falukant_data."character"
|
||
WHERE user_id IS NOT NULL;
|
||
"#;
|
||
|
||
/// Spieler-Charaktere mit Health <= 0 werden periodisch zum Tod verarbeitet.
|
||
pub const QUERY_GET_CHARACTERS_ZERO_HEALTH: &str = r#"
|
||
SELECT id FROM falukant_data."character"
|
||
WHERE user_id IS NOT NULL AND health <= 0;
|
||
"#;
|
||
|
||
// politics worker queries
|
||
pub const QUERY_COUNT_OFFICES_PER_REGION: &str = r#"
|
||
WITH
|
||
seats_per_region AS (
|
||
SELECT
|
||
pot.id AS office_type_id,
|
||
rt.id AS region_id,
|
||
pot.seats_per_region AS seats_total
|
||
FROM falukant_type.political_office_type AS pot
|
||
JOIN falukant_type.region AS rt
|
||
ON pot.region_type = rt.label_tr
|
||
),
|
||
occupied AS (
|
||
SELECT
|
||
po.office_type_id,
|
||
po.region_id,
|
||
COUNT(*) AS occupied_count
|
||
FROM falukant_data.political_office AS po
|
||
GROUP BY po.office_type_id, po.region_id
|
||
),
|
||
combined AS (
|
||
SELECT
|
||
spr.region_id,
|
||
spr.seats_total AS required_count,
|
||
COALESCE(o.occupied_count, 0) AS occupied_count
|
||
FROM seats_per_region AS spr
|
||
LEFT JOIN occupied AS o
|
||
ON spr.office_type_id = o.office_type_id
|
||
AND spr.region_id = o.region_id
|
||
)
|
||
SELECT
|
||
region_id,
|
||
SUM(required_count) AS required_count,
|
||
SUM(occupied_count) AS occupied_count
|
||
FROM combined
|
||
GROUP BY region_id;
|
||
"#;
|
||
|
||
pub const QUERY_FIND_OFFICE_GAPS: &str = r#"
|
||
WITH
|
||
seats AS (
|
||
SELECT
|
||
pot.id AS office_type_id,
|
||
rt.id AS region_id,
|
||
pot.seats_per_region AS seats_total
|
||
FROM falukant_type.political_office_type AS pot
|
||
JOIN falukant_type.region AS rt
|
||
ON pot.region_type = rt.label_tr
|
||
),
|
||
occupied AS (
|
||
SELECT
|
||
po.office_type_id,
|
||
po.region_id,
|
||
COUNT(*) AS occupied_count
|
||
FROM falukant_data.political_office AS po
|
||
GROUP BY po.office_type_id, po.region_id
|
||
)
|
||
SELECT
|
||
s.office_type_id,
|
||
s.region_id,
|
||
(s.seats_total - COALESCE(o.occupied_count, 0)) AS gaps
|
||
FROM seats AS s
|
||
LEFT JOIN occupied AS o
|
||
ON s.office_type_id = o.office_type_id
|
||
AND s.region_id = o.region_id
|
||
WHERE (s.seats_total - COALESCE(o.occupied_count, 0)) > 0;
|
||
"#;
|
||
|
||
pub const QUERY_SELECT_NEEDED_ELECTIONS: &str = r#"
|
||
WITH
|
||
target_date AS (
|
||
SELECT NOW()::date AS election_date
|
||
),
|
||
expired_today AS (
|
||
DELETE FROM falukant_data.political_office AS po
|
||
USING falukant_type.political_office_type AS pot
|
||
WHERE po.office_type_id = pot.id
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))::date
|
||
= (SELECT election_date FROM target_date)
|
||
RETURNING
|
||
pot.id AS office_type_id,
|
||
po.region_id AS region_id
|
||
),
|
||
gaps_per_region AS (
|
||
SELECT
|
||
office_type_id,
|
||
region_id,
|
||
COUNT(*) AS gaps
|
||
FROM expired_today
|
||
GROUP BY office_type_id, region_id
|
||
),
|
||
to_schedule AS (
|
||
SELECT
|
||
g.office_type_id,
|
||
g.region_id,
|
||
g.gaps,
|
||
td.election_date
|
||
FROM gaps_per_region AS g
|
||
CROSS JOIN target_date AS td
|
||
WHERE NOT EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.election AS e
|
||
WHERE e.office_type_id = g.office_type_id
|
||
AND e.region_id = g.region_id
|
||
AND e.date::date = td.election_date
|
||
)
|
||
),
|
||
new_elections AS (
|
||
INSERT INTO falukant_data.election
|
||
(office_type_id, date, posts_to_fill, created_at, updated_at, region_id)
|
||
SELECT
|
||
ts.office_type_id,
|
||
ts.election_date,
|
||
ts.gaps,
|
||
NOW(),
|
||
NOW(),
|
||
ts.region_id
|
||
FROM to_schedule AS ts
|
||
RETURNING
|
||
id AS election_id,
|
||
region_id,
|
||
posts_to_fill
|
||
)
|
||
SELECT
|
||
ne.election_id,
|
||
ne.region_id,
|
||
ne.posts_to_fill
|
||
FROM new_elections AS ne
|
||
ORDER BY ne.region_id, ne.election_id;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_CANDIDATES: &str = r#"
|
||
INSERT INTO falukant_data.candidate
|
||
(election_id, character_id, created_at, updated_at)
|
||
SELECT
|
||
$1 AS election_id,
|
||
sub.id AS character_id,
|
||
NOW() AS created_at,
|
||
NOW() AS updated_at
|
||
FROM (
|
||
WITH RECURSIVE region_tree AS (
|
||
SELECT r.id
|
||
FROM falukant_data.region AS r
|
||
WHERE r.id = $2
|
||
UNION ALL
|
||
SELECT r2.id
|
||
FROM falukant_data.region AS r2
|
||
JOIN region_tree AS rt
|
||
ON r2.parent_id = rt.id
|
||
)
|
||
SELECT ch.id
|
||
FROM falukant_data.character AS ch
|
||
JOIN region_tree AS rt2
|
||
ON ch.region_id = rt2.id
|
||
WHERE ch.user_id IS NULL
|
||
AND ch.birthdate <= NOW() - INTERVAL '21 days'
|
||
AND ch.title_of_nobility IN (
|
||
SELECT id
|
||
FROM falukant_type.title
|
||
WHERE label_tr != 'noncivil'
|
||
)
|
||
ORDER BY RANDOM()
|
||
LIMIT ($3 * 2)
|
||
) AS sub(id);
|
||
"#;
|
||
|
||
pub const QUERY_SELECT_ELECTIONS_NEEDING_CANDIDATES: &str = r#"
|
||
SELECT
|
||
e.id AS election_id,
|
||
e.region_id AS region_id,
|
||
e.posts_to_fill
|
||
FROM falukant_data.election AS e
|
||
WHERE e.region_id IS NOT NULL
|
||
AND e.posts_to_fill > 0
|
||
AND e.date::date >= CURRENT_DATE
|
||
AND NOT EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.candidate AS c
|
||
WHERE c.election_id = e.id
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_PROCESS_EXPIRED_AND_FILL: &str = r#"
|
||
WITH
|
||
expired_offices AS (
|
||
DELETE FROM falukant_data.political_office AS po
|
||
USING falukant_type.political_office_type AS pot
|
||
WHERE po.office_type_id = pot.id
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) <= NOW()
|
||
RETURNING
|
||
pot.id AS office_type_id,
|
||
po.region_id AS region_id
|
||
),
|
||
distinct_types AS (
|
||
SELECT DISTINCT office_type_id, region_id FROM expired_offices
|
||
),
|
||
votes_per_candidate AS (
|
||
SELECT
|
||
dt.office_type_id,
|
||
dt.region_id,
|
||
c.character_id,
|
||
COUNT(v.id) AS vote_count
|
||
FROM distinct_types AS dt
|
||
JOIN falukant_data.election AS e
|
||
ON e.office_type_id = dt.office_type_id
|
||
JOIN falukant_data.vote AS v
|
||
ON v.election_id = e.id
|
||
JOIN falukant_data.candidate AS c
|
||
ON c.election_id = e.id
|
||
AND c.id = v.candidate_id
|
||
WHERE e.date >= (NOW() - INTERVAL '30 days')
|
||
GROUP BY dt.office_type_id, dt.region_id, c.character_id
|
||
),
|
||
ranked_winners AS (
|
||
SELECT
|
||
vpc.office_type_id,
|
||
vpc.region_id,
|
||
vpc.character_id,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY vpc.office_type_id, vpc.region_id
|
||
ORDER BY vpc.vote_count DESC
|
||
) AS rn
|
||
FROM votes_per_candidate AS vpc
|
||
),
|
||
selected_winners AS (
|
||
SELECT
|
||
rw.office_type_id,
|
||
rw.region_id,
|
||
rw.character_id
|
||
FROM ranked_winners AS rw
|
||
JOIN falukant_type.political_office_type AS pot
|
||
ON pot.id = rw.office_type_id
|
||
WHERE rw.rn <= pot.seats_per_region
|
||
),
|
||
insert_winners AS (
|
||
INSERT INTO falukant_data.political_office
|
||
(office_type_id, character_id, created_at, updated_at, region_id)
|
||
SELECT
|
||
sw.office_type_id,
|
||
sw.character_id,
|
||
NOW(),
|
||
NOW(),
|
||
sw.region_id
|
||
FROM selected_winners AS sw
|
||
RETURNING id AS new_office_id, office_type_id, character_id, region_id
|
||
),
|
||
count_inserted AS (
|
||
SELECT
|
||
office_type_id,
|
||
region_id,
|
||
COUNT(*) AS inserted_count
|
||
FROM insert_winners
|
||
GROUP BY office_type_id, region_id
|
||
),
|
||
needed_to_fill AS (
|
||
SELECT
|
||
dt.office_type_id,
|
||
dt.region_id,
|
||
(pot.seats_per_region - COALESCE(ci.inserted_count, 0)) AS gaps
|
||
FROM distinct_types AS dt
|
||
JOIN falukant_type.political_office_type AS pot
|
||
ON pot.id = dt.office_type_id
|
||
LEFT JOIN count_inserted AS ci
|
||
ON ci.office_type_id = dt.office_type_id
|
||
AND ci.region_id = dt.region_id
|
||
WHERE (pot.seats_per_region - COALESCE(ci.inserted_count, 0)) > 0
|
||
),
|
||
random_candidates AS (
|
||
SELECT
|
||
rtf.office_type_id,
|
||
rtf.region_id,
|
||
ch.id AS character_id,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY rtf.office_type_id, rtf.region_id
|
||
ORDER BY RANDOM()
|
||
) AS rn
|
||
FROM needed_to_fill AS rtf
|
||
JOIN falukant_data.character AS ch
|
||
ON ch.region_id IN (
|
||
WITH RECURSIVE region_tree AS (
|
||
SELECT id FROM falukant_data.region WHERE id = rtf.region_id
|
||
UNION ALL
|
||
SELECT r2.id FROM falukant_data.region r2
|
||
JOIN region_tree rt ON r2.parent_id = rt.id
|
||
)
|
||
SELECT id FROM region_tree
|
||
)
|
||
AND ch.user_id IS NULL
|
||
AND ch.birthdate <= NOW() - INTERVAL '21 days'
|
||
AND ch.title_of_nobility IN (
|
||
SELECT id FROM falukant_type.title WHERE label_tr != 'noncivil'
|
||
)
|
||
AND NOT EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.political_office AS po2
|
||
JOIN falukant_type.political_office_type AS pot2
|
||
ON pot2.id = po2.office_type_id
|
||
WHERE po2.character_id = ch.id
|
||
AND (po2.created_at + (pot2.term_length * INTERVAL '1 day')) >
|
||
NOW() + INTERVAL '2 days'
|
||
)
|
||
),
|
||
insert_random AS (
|
||
INSERT INTO falukant_data.political_office
|
||
(office_type_id, character_id, created_at, updated_at, region_id)
|
||
SELECT
|
||
rc.office_type_id,
|
||
rc.character_id,
|
||
NOW(),
|
||
NOW(),
|
||
rc.region_id
|
||
FROM random_candidates AS rc
|
||
JOIN needed_to_fill AS rtf
|
||
ON rtf.office_type_id = rc.office_type_id
|
||
AND rtf.region_id = rc.region_id
|
||
WHERE rc.rn <= rtf.gaps
|
||
RETURNING id AS new_office_id, office_type_id, character_id, region_id
|
||
)
|
||
SELECT
|
||
new_office_id AS office_id,
|
||
office_type_id,
|
||
character_id,
|
||
region_id
|
||
FROM insert_winners
|
||
UNION ALL
|
||
SELECT
|
||
new_office_id AS office_id,
|
||
office_type_id,
|
||
character_id,
|
||
region_id
|
||
FROM insert_random;
|
||
"#;
|
||
|
||
pub const QUERY_USERS_IN_CITIES_OF_REGIONS: &str = r#"
|
||
WITH RECURSIVE region_tree AS (
|
||
SELECT id
|
||
FROM falukant_data.region
|
||
WHERE id = $1
|
||
UNION ALL
|
||
SELECT r2.id
|
||
FROM falukant_data.region AS r2
|
||
JOIN region_tree AS rt
|
||
ON r2.parent_id = rt.id
|
||
)
|
||
SELECT DISTINCT ch.user_id
|
||
FROM falukant_data.character AS ch
|
||
JOIN region_tree AS rt2
|
||
ON ch.region_id = rt2.id
|
||
WHERE ch.user_id IS NOT NULL;
|
||
"#;
|
||
|
||
pub const QUERY_NOTIFY_OFFICE_EXPIRATION: &str = r#"
|
||
INSERT INTO falukant_log.notification
|
||
(user_id, tr, created_at, updated_at)
|
||
SELECT
|
||
ch.user_id,
|
||
'notify_office_expiring',
|
||
NOW(),
|
||
NOW()
|
||
FROM falukant_data.political_office AS po
|
||
JOIN falukant_type.political_office_type AS pot
|
||
ON po.office_type_id = pot.id
|
||
JOIN falukant_data.character AS ch
|
||
ON ch.id = po.character_id
|
||
WHERE ch.user_id IS NOT NULL
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))
|
||
BETWEEN (NOW() + INTERVAL '2 days')
|
||
AND (NOW() + INTERVAL '2 days' + INTERVAL '1 second');
|
||
"#;
|
||
|
||
pub const QUERY_NOTIFY_ELECTION_CREATED: &str = r#"
|
||
INSERT INTO falukant_log.notification
|
||
(user_id, tr, created_at, updated_at)
|
||
VALUES
|
||
($1, 'notify_election_created', NOW(), NOW());
|
||
"#;
|
||
|
||
pub const QUERY_NOTIFY_OFFICE_FILLED: &str = r#"
|
||
INSERT INTO falukant_log.notification
|
||
(user_id, tr, created_at, updated_at)
|
||
VALUES
|
||
((SELECT user_id FROM falukant_data.character WHERE id = $1), 'notify_office_filled', NOW(), NOW());
|
||
"#;
|
||
|
||
pub const QUERY_GET_USERS_WITH_EXPIRING_OFFICES: &str = r#"
|
||
SELECT DISTINCT ch.user_id
|
||
FROM falukant_data.political_office AS po
|
||
JOIN falukant_type.political_office_type AS pot
|
||
ON po.office_type_id = pot.id
|
||
JOIN falukant_data.character AS ch
|
||
ON po.character_id = ch.id
|
||
WHERE ch.user_id IS NOT NULL
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day'))
|
||
BETWEEN (NOW() + INTERVAL '2 days')
|
||
AND (NOW() + INTERVAL '2 days' + INTERVAL '1 second');
|
||
"#;
|
||
|
||
pub const QUERY_GET_USERS_IN_REGIONS_WITH_ELECTIONS: &str = r#"
|
||
SELECT DISTINCT ch.user_id
|
||
FROM falukant_data.election AS e
|
||
JOIN falukant_data.character AS ch
|
||
ON ch.region_id = e.region_id
|
||
WHERE ch.user_id IS NOT NULL
|
||
AND e.date >= NOW() - INTERVAL '1 day';
|
||
"#;
|
||
|
||
pub const QUERY_GET_USERS_WITH_FILLED_OFFICES: &str = r#"
|
||
SELECT DISTINCT ch.user_id
|
||
FROM falukant_data.political_office AS po
|
||
JOIN falukant_data.character AS ch
|
||
ON po.character_id = ch.id
|
||
WHERE ch.user_id IS NOT NULL
|
||
AND po.created_at >= NOW() - INTERVAL '1 minute';
|
||
"#;
|
||
|
||
pub const QUERY_PROCESS_ELECTIONS: &str = r#"
|
||
SELECT office_id, office_type_id, character_id, region_id
|
||
FROM falukant_data.process_elections();
|
||
"#;
|
||
|
||
/// Wahlergebnis für Spieler-Kandidaten (`falukant_data.candidate` + `character.user_id IS NOT NULL`).
|
||
/// Läuft nach `process_elections()`; Deduplizierung über bestehende `election_result`-Notifications (`tr` JSON).
|
||
pub const QUERY_PLAYER_ELECTION_RESULT_ROWS: &str = r#"
|
||
WITH vote_counts AS (
|
||
SELECT c.id AS candidate_id,
|
||
c.election_id,
|
||
c.character_id,
|
||
COUNT(v.id)::bigint AS vote_count
|
||
FROM falukant_data.candidate c
|
||
LEFT JOIN falukant_data.vote v ON v.candidate_id = c.id
|
||
GROUP BY c.id, c.election_id, c.character_id
|
||
),
|
||
top_per_election AS (
|
||
SELECT DISTINCT ON (vc.election_id)
|
||
vc.election_id,
|
||
vc.character_id AS top_character_id,
|
||
vc.vote_count AS top_votes
|
||
FROM vote_counts vc
|
||
ORDER BY vc.election_id, vc.vote_count DESC, vc.candidate_id
|
||
),
|
||
eligible AS (
|
||
SELECT e.id AS election_id,
|
||
e.office_type_id,
|
||
e.region_id,
|
||
e.posts_to_fill,
|
||
vc.candidate_id,
|
||
vc.character_id,
|
||
vc.vote_count,
|
||
ch.user_id
|
||
FROM falukant_data.election e
|
||
JOIN vote_counts vc ON vc.election_id = e.id
|
||
JOIN falukant_data.character ch ON ch.id = vc.character_id
|
||
WHERE ch.user_id IS NOT NULL
|
||
AND e.date::date <= CURRENT_DATE
|
||
AND e.date::date >= CURRENT_DATE - INTERVAL '7 days'
|
||
AND NOT EXISTS (
|
||
SELECT 1
|
||
FROM falukant_log.notification n
|
||
WHERE n.user_id = ch.user_id
|
||
AND n.tr LIKE '{%'
|
||
AND (n.tr::jsonb->>'event') = 'election_result'
|
||
AND (n.tr::jsonb->>'election_id')::int = e.id
|
||
)
|
||
),
|
||
ranked AS (
|
||
SELECT vc.candidate_id,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY vc.election_id
|
||
ORDER BY vc.vote_count DESC, vc.candidate_id
|
||
) AS rank_in_election
|
||
FROM vote_counts vc
|
||
)
|
||
SELECT elig.user_id,
|
||
elig.election_id,
|
||
elig.office_type_id,
|
||
COALESCE(pot.name::text, '') AS office_name,
|
||
elig.region_id,
|
||
COALESCE(NULLIF(TRIM(dr.name::text), ''), elig.region_id::text) AS region_name,
|
||
elig.character_id,
|
||
elig.candidate_id,
|
||
elig.vote_count AS your_votes,
|
||
COALESCE(rk.rank_in_election, 0)::int AS rank_in_election,
|
||
EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.political_office po
|
||
WHERE po.character_id = elig.character_id
|
||
AND po.office_type_id = elig.office_type_id
|
||
AND po.region_id = elig.region_id
|
||
AND po.created_at >= (CURRENT_TIMESTAMP - INTERVAL '3 days')
|
||
) AS won,
|
||
tpe.top_character_id,
|
||
tpe.top_votes,
|
||
elig.posts_to_fill
|
||
FROM eligible elig
|
||
JOIN falukant_type.political_office_type pot ON pot.id = elig.office_type_id
|
||
LEFT JOIN falukant_data.region dr ON dr.id = elig.region_id
|
||
LEFT JOIN top_per_election tpe ON tpe.election_id = elig.election_id
|
||
LEFT JOIN ranked rk ON rk.candidate_id = elig.candidate_id
|
||
ORDER BY elig.election_id, elig.user_id;
|
||
"#;
|
||
|
||
// --- Politische Amtsvorteile (reputation_periodic, free_lover_slots, Ernennungs-Ablauf) ---
|
||
|
||
/// `political_benefit_last_tick` + `falukant_predefine.political_office_benefit` vorhanden.
|
||
pub const QUERY_POLITICAL_BENEFIT_DAEMON_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.tables
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'political_benefit_last_tick'
|
||
) AND EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.tables
|
||
WHERE table_schema = 'falukant_predefine'
|
||
AND table_name = 'political_office_benefit'
|
||
) AS ready;
|
||
"#;
|
||
|
||
/// Spieler mit aktivem Amt + `reputation_periodic` (JSON `tr` oder `benefitType`); Kalendertage seit `last_tick_at`.
|
||
pub const QUERY_POLITICAL_REPUTATION_TICK_ROWS: &str = r#"
|
||
SELECT
|
||
pob.id AS benefit_id,
|
||
po.character_id AS character_id,
|
||
GREATEST(1, COALESCE(NULLIF((pob.value::jsonb->>'intervalDays'), '')::int, 7)) AS interval_days,
|
||
COALESCE(NULLIF((pob.value::jsonb->>'gain'), '')::numeric, 0::numeric) AS gain,
|
||
ch.user_id AS falukant_user_id
|
||
FROM falukant_data.political_office po
|
||
JOIN falukant_predefine.political_office_benefit pob
|
||
ON pob.political_office_type_id = po.office_type_id
|
||
JOIN falukant_data.character ch ON ch.id = po.character_id
|
||
LEFT JOIN falukant_data.political_benefit_last_tick btl
|
||
ON btl.character_id = po.character_id
|
||
AND btl.political_office_benefit_id = pob.id
|
||
WHERE po.character_id IS NOT NULL
|
||
AND ch.user_id IS NOT NULL
|
||
AND COALESCE(pob.value::jsonb->>'tr', pob.value::jsonb->>'benefitType') = 'reputation_periodic'
|
||
AND COALESCE(NULLIF((pob.value::jsonb->>'gain'), '')::numeric, 0::numeric) > 0
|
||
AND (
|
||
btl.last_tick_at IS NULL
|
||
OR (CURRENT_DATE - (btl.last_tick_at::date))
|
||
>= GREATEST(1, COALESCE(NULLIF((pob.value::jsonb->>'intervalDays'), '')::int, 7))
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_POLITICAL_REPUTATION_TICK_UPSERT: &str = r#"
|
||
INSERT INTO falukant_data.political_benefit_last_tick
|
||
(character_id, political_office_benefit_id, last_tick_at, ticks_count)
|
||
VALUES ($1::int, $2::int, NOW(), 1)
|
||
ON CONFLICT (character_id, political_office_benefit_id)
|
||
DO UPDATE SET
|
||
last_tick_at = EXCLUDED.last_tick_at,
|
||
ticks_count = falukant_data.political_benefit_last_tick.ticks_count + 1;
|
||
"#;
|
||
|
||
pub const QUERY_POLITICAL_REPUTATION_APPLY_GAIN: &str = r#"
|
||
UPDATE falukant_data.character c
|
||
SET reputation = LEAST(
|
||
100::numeric,
|
||
GREATEST(0::numeric, COALESCE(c.reputation, 50::numeric) + $gain::numeric)
|
||
),
|
||
updated_at = NOW()
|
||
WHERE c.id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_POLITICAL_APPOINTMENT_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.tables
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'political_appointment'
|
||
) AS ready;
|
||
"#;
|
||
|
||
pub const QUERY_POLITICAL_APPOINTMENT_EXPIRE_PENDING: &str = r#"
|
||
UPDATE falukant_data.political_appointment
|
||
SET status = 'expired',
|
||
updated_at = NOW()
|
||
WHERE status = 'pending'
|
||
AND expires_at IS NOT NULL
|
||
AND expires_at < NOW();
|
||
"#;
|
||
|
||
/// Spalte `last_political_daily_salary_on` (Migration `013_falukant_political_daily_salary.sql`).
|
||
pub const QUERY_POLITICAL_DAILY_SALARY_USER_COLUMN_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'falukant_user'
|
||
AND column_name = 'last_political_daily_salary_on'
|
||
) AS ready;
|
||
"#;
|
||
|
||
/// Aktive Ämter mit Spieler-Charakter, noch **kein** Gehalt heute (UTC).
|
||
/// `configured_daily_salary`: Summe aus `political_office_benefit` mit `daily_salary` im JSON; sonst 0 → Fallback im Daemon nach Amts-Rang.
|
||
pub const QUERY_POLITICAL_DAILY_SALARY_OFFICE_ROWS: &str = r#"
|
||
SELECT
|
||
ch.user_id AS falukant_user_id,
|
||
COALESCE(pot.name, '') AS office_name,
|
||
COALESCE((
|
||
SELECT SUM(GREATEST(0, COALESCE(NULLIF(TRIM(sb.value::jsonb->>'daily_salary'), '')::numeric, 0)))
|
||
FROM falukant_predefine.political_office_benefit sb
|
||
WHERE sb.political_office_type_id = po.office_type_id
|
||
AND COALESCE(sb.value::jsonb->>'tr', sb.value::jsonb->>'benefitType') = 'daily_salary'
|
||
), 0::numeric) AS configured_daily_salary
|
||
FROM falukant_data.political_office po
|
||
JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id
|
||
JOIN falukant_data.character ch ON ch.id = po.character_id
|
||
JOIN falukant_data.falukant_user fu ON fu.id = ch.user_id
|
||
WHERE ch.user_id IS NOT NULL
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) > NOW()
|
||
AND (fu.last_political_daily_salary_on IS NULL OR fu.last_political_daily_salary_on < CURRENT_DATE);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_LAST_POLITICAL_DAILY_SALARY_ON: &str = r#"
|
||
UPDATE falukant_data.falukant_user
|
||
SET last_political_daily_salary_on = CURRENT_DATE,
|
||
updated_at = NOW()
|
||
WHERE id = $1::int
|
||
AND (last_political_daily_salary_on IS NULL OR last_political_daily_salary_on < CURRENT_DATE);
|
||
"#;
|
||
|
||
/// Summe `count` aus `free_lover_slots`-Benefits (JSON `tr`/`benefitType`), gedeckelt.
|
||
pub const QUERY_SUM_FREE_LOVER_SLOTS_FOR_CHARACTER: &str = r#"
|
||
SELECT LEAST(
|
||
5,
|
||
COALESCE(SUM(
|
||
GREATEST(0, COALESCE(NULLIF((pob.value::jsonb->>'count'), '')::int, 0))
|
||
), 0)
|
||
)::int AS free_slots
|
||
FROM falukant_data.political_office po
|
||
JOIN falukant_predefine.political_office_benefit pob
|
||
ON pob.political_office_type_id = po.office_type_id
|
||
WHERE po.character_id = $1::int
|
||
AND COALESCE(pob.value::jsonb->>'tr', pob.value::jsonb->>'benefitType') = 'free_lover_slots';
|
||
"#;
|
||
|
||
pub const QUERY_TRIM_EXCESS_OFFICES_GLOBAL: &str = r#"
|
||
WITH seats AS (
|
||
SELECT
|
||
pot.id AS office_type_id,
|
||
rt.id AS region_id,
|
||
pot.seats_per_region AS seats_total
|
||
FROM falukant_type.political_office_type AS pot
|
||
JOIN falukant_type.region AS rt
|
||
ON pot.region_type = rt.label_tr
|
||
),
|
||
ranked AS (
|
||
SELECT
|
||
po.id,
|
||
po.office_type_id,
|
||
po.region_id,
|
||
s.seats_total,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY po.office_type_id, po.region_id
|
||
ORDER BY po.created_at DESC
|
||
) AS rn
|
||
FROM falukant_data.political_office AS po
|
||
JOIN seats AS s
|
||
ON s.office_type_id = po.office_type_id
|
||
AND s.region_id = po.region_id
|
||
),
|
||
to_delete AS (
|
||
SELECT id
|
||
FROM ranked
|
||
WHERE rn > seats_total
|
||
)
|
||
DELETE FROM falukant_data.political_office
|
||
WHERE id IN (SELECT id FROM to_delete);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_CHARACTERS_HEALTH: &str = r#"
|
||
UPDATE falukant_data."character"
|
||
SET health = $1
|
||
WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_MOOD: &str = r#"
|
||
UPDATE falukant_data."character" AS c
|
||
SET mood_id = falukant_data.get_random_mood_id()
|
||
WHERE c.health > 0
|
||
AND random() < (1.0 / 50.0);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_GET_ITEMS_TO_UPDATE: &str = r#"
|
||
SELECT id, product_id, producer_id, quantity
|
||
FROM falukant_log.production p
|
||
WHERE p.production_timestamp::date < current_date;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_GET_CHARACTER_IDS: &str = r#"
|
||
SELECT fu.id AS user_id,
|
||
c.id AS character_id,
|
||
c2.id AS director_id
|
||
FROM falukant_data.falukant_user fu
|
||
JOIN falukant_data.character c
|
||
ON c.user_id = fu.id
|
||
LEFT JOIN falukant_data.director d
|
||
ON d.employer_user_id = fu.id
|
||
LEFT JOIN falukant_data.character c2
|
||
ON c2.id = d.director_character_id
|
||
WHERE fu.id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_KNOWLEDGE: &str = r#"
|
||
UPDATE falukant_data.knowledge
|
||
SET knowledge = LEAST(knowledge + $3, 100)
|
||
WHERE character_id = $1
|
||
AND product_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_LOG_ENTRY: &str = r#"
|
||
DELETE FROM falukant_log.production
|
||
WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_OPEN_CREDITS: &str = r#"
|
||
SELECT
|
||
c.id AS credit_id,
|
||
c.amount,
|
||
c.remaining_amount,
|
||
c.interest_rate,
|
||
fu.id AS user_id,
|
||
fu.money,
|
||
c2.id AS character_id,
|
||
dp.created_at AS debitor_prism_start,
|
||
dp.created_at::date < current_date AS prism_started_previously
|
||
FROM falukant_data.credit c
|
||
JOIN falukant_data.falukant_user fu
|
||
ON fu.id = c.falukant_user_id
|
||
JOIN falukant_data.character c2
|
||
ON c2.user_id = c.falukant_user_id
|
||
LEFT JOIN falukant_data.debtors_prism dp
|
||
ON dp.character_id = c2.id
|
||
WHERE c.remaining_amount > 0
|
||
AND c.updated_at::date < current_date;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_CREDIT: &str = r#"
|
||
UPDATE falukant_data.credit c
|
||
SET remaining_amount = $1
|
||
WHERE falukant_user_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_CLEANUP_CREDITS: &str = r#"
|
||
DELETE FROM falukant_data.credit
|
||
WHERE remaining_amount <= 0.01;
|
||
"#;
|
||
|
||
// --- Falukant: Schuldturm & Pfändung (docs/FALUKANT_DEBTORS_DAEMON.md) ---
|
||
pub const QUERY_DEBTORS_PRISM_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'debtors_prism'
|
||
AND column_name = 'days_overdue'
|
||
) AS ready;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_CREDIT_USERS_FOR_DAILY: &str = r#"
|
||
SELECT
|
||
c.falukant_user_id AS user_id,
|
||
MIN(ch.id) AS character_id,
|
||
COALESCE(fu.money, 0)::float8 AS money,
|
||
COALESCE(SUM(
|
||
c.amount::float8 / 10.0
|
||
+ c.amount::float8 * c.interest_rate::float8 / 100.0
|
||
), 0)::float8 AS total_pay_rate,
|
||
COALESCE(SUM(c.remaining_amount), 0)::float8 AS total_credit_remaining
|
||
FROM falukant_data.credit c
|
||
JOIN falukant_data.falukant_user fu ON fu.id = c.falukant_user_id
|
||
JOIN falukant_data.character ch ON ch.user_id = c.falukant_user_id AND ch.health > 0
|
||
WHERE c.remaining_amount > 0.01
|
||
GROUP BY c.falukant_user_id, fu.money;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_GET_PRISM_BY_CHARACTER: &str = r#"
|
||
SELECT
|
||
dp.id,
|
||
dp.character_id,
|
||
COALESCE(dp.status, '') AS status,
|
||
COALESCE(dp.days_overdue, 0)::int AS days_overdue,
|
||
COALESCE(dp.remaining_debt, 0)::float8 AS remaining_debt,
|
||
dp.entered_at::text AS entered_at,
|
||
dp.released_at::text AS released_at
|
||
FROM falukant_data.debtors_prism dp
|
||
WHERE dp.character_id = $1::int
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_INSERT_DELINQUENT: &str = r#"
|
||
INSERT INTO falukant_data.debtors_prism (
|
||
character_id,
|
||
status,
|
||
days_overdue,
|
||
remaining_debt,
|
||
next_forced_action,
|
||
reason,
|
||
creditworthiness_penalty,
|
||
public_known,
|
||
assets_seized_json
|
||
)
|
||
SELECT
|
||
$1::int,
|
||
'delinquent',
|
||
1,
|
||
$2::float8,
|
||
'reminder',
|
||
'delinquent',
|
||
0,
|
||
false,
|
||
'{}'::jsonb
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM falukant_data.debtors_prism d2 WHERE d2.character_id = $1::int
|
||
)
|
||
RETURNING id;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_INCREMENT_DELINQUENT: &str = r#"
|
||
UPDATE falukant_data.debtors_prism
|
||
SET days_overdue = COALESCE(days_overdue, 0) + 1,
|
||
remaining_debt = $2::float8,
|
||
next_forced_action = CASE
|
||
WHEN COALESCE(days_overdue, 0) + 1 >= 3 THEN 'asset_seizure'
|
||
WHEN COALESCE(days_overdue, 0) + 1 = 2 THEN 'final_warning'
|
||
ELSE 'reminder'
|
||
END,
|
||
updated_at = NOW()
|
||
WHERE character_id = $1::int
|
||
AND status = 'delinquent'
|
||
RETURNING id, COALESCE(days_overdue, 0) AS new_days;
|
||
"#;
|
||
|
||
/// Nach abgeschlossenem Fall (`released`) neuer Verzug: Zeile wieder auf Delinquent setzen.
|
||
pub const QUERY_DEBTORS_REACTIVATE_DELINQUENT_FROM_RELEASED: &str = r#"
|
||
UPDATE falukant_data.debtors_prism
|
||
SET status = 'delinquent',
|
||
days_overdue = 1,
|
||
remaining_debt = $2::float8,
|
||
next_forced_action = 'reminder',
|
||
reason = 'delinquent',
|
||
updated_at = NOW()
|
||
WHERE character_id = $1::int
|
||
AND status = 'released'
|
||
RETURNING id;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_RESET_DELINQUENCY_SOLVENT: &str = r#"
|
||
UPDATE falukant_data.debtors_prism
|
||
SET days_overdue = 0,
|
||
next_forced_action = 'reminder',
|
||
updated_at = NOW()
|
||
WHERE character_id = $1::int
|
||
AND status = 'delinquent';
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_RESET_ON_PAYMENT_SUCCESS: &str = r#"
|
||
UPDATE falukant_data.debtors_prism
|
||
SET days_overdue = 0,
|
||
next_forced_action = 'reminder',
|
||
updated_at = NOW()
|
||
WHERE character_id = $1::int
|
||
AND status = 'delinquent';
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_ENTER_PRISON: &str = r#"
|
||
UPDATE falukant_data.debtors_prism
|
||
SET status = 'imprisoned',
|
||
entered_at = COALESCE(entered_at, NOW()),
|
||
released_at = NULL,
|
||
debt_at_entry = $2::float8,
|
||
remaining_debt = $2::float8,
|
||
reason = 'credit_default',
|
||
creditworthiness_penalty = COALESCE(creditworthiness_penalty, 0) + 45,
|
||
next_forced_action = 'asset_seizure',
|
||
public_known = true,
|
||
updated_at = NOW()
|
||
WHERE character_id = $1::int
|
||
AND status = 'delinquent'
|
||
AND COALESCE(days_overdue, 0) >= 3
|
||
RETURNING id;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_RELEASE_IF_PAID: &str = r#"
|
||
UPDATE falukant_data.debtors_prism dp
|
||
SET status = 'released',
|
||
released_at = NOW(),
|
||
next_forced_action = NULL,
|
||
days_overdue = 0,
|
||
remaining_debt = 0,
|
||
updated_at = NOW()
|
||
FROM falukant_data.character ch
|
||
WHERE ch.id = dp.character_id
|
||
AND ch.user_id = $1::int
|
||
AND dp.status = 'imprisoned'
|
||
AND COALESCE((
|
||
SELECT SUM(c.remaining_amount)
|
||
FROM falukant_data.credit c
|
||
WHERE c.falukant_user_id = $1::int
|
||
), 0) <= 0.01
|
||
RETURNING dp.character_id;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_SUBTRACT_REPUTATION: &str = r#"
|
||
UPDATE falukant_data.character
|
||
SET reputation = GREATEST(0::numeric, COALESCE(reputation, 50::numeric) - $2::numeric),
|
||
updated_at = NOW()
|
||
WHERE id = $1::int AND user_id IS NOT NULL;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_MARRIAGE_SATISFACTION_SUB: &str = r#"
|
||
UPDATE falukant_data.relationship r
|
||
SET marriage_satisfaction = GREATEST(0, COALESCE(r.marriage_satisfaction, 55) - $2::int),
|
||
updated_at = NOW()
|
||
FROM falukant_type.relationship rt
|
||
WHERE rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
AND (r.character1_id = $1::int OR r.character2_id = $1::int);
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_HOUSEHOLD_TENSION_ADD: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET household_tension_score = LEAST(100, COALESCE(household_tension_score, 0) + $2::int),
|
||
updated_at = NOW()
|
||
WHERE user_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_LOVER_AFFECTION_SUB: &str = r#"
|
||
UPDATE falukant_data.relationship_state rs
|
||
SET affection = GREATEST(0, COALESCE(rs.affection, 50) - $2::int),
|
||
updated_at = NOW()
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
WHERE rs.relationship_id = r.id
|
||
AND rs.active = true
|
||
AND (r.character1_id = $1::int OR r.character2_id = $1::int);
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_IMPRISONED_REP_MALUS: &str = r#"
|
||
UPDATE falukant_data.character c
|
||
SET reputation = GREATEST(0::numeric, COALESCE(c.reputation, 50::numeric) - $2::numeric),
|
||
updated_at = NOW()
|
||
WHERE c.id = $1::int AND c.user_id IS NOT NULL;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_IMPRISONED_PENALTY_PLUS1: &str = r#"
|
||
UPDATE falukant_data.debtors_prism
|
||
SET creditworthiness_penalty = COALESCE(creditworthiness_penalty, 0) + 1,
|
||
updated_at = NOW()
|
||
WHERE character_id = $1::int AND status = 'imprisoned';
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_IMPRISONED_MARRIAGE_SUB1: &str = r#"
|
||
UPDATE falukant_data.relationship r
|
||
SET marriage_satisfaction = GREATEST(0, COALESCE(r.marriage_satisfaction, 55) - 1),
|
||
updated_at = NOW()
|
||
FROM falukant_type.relationship rt
|
||
WHERE rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
AND (r.character1_id = $1::int OR r.character2_id = $1::int);
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_IMPRISONED_TENSION_PLUS2: &str = r#"
|
||
UPDATE falukant_data.user_house uh
|
||
SET household_tension_score = LEAST(100, COALESCE(uh.household_tension_score, 0) + 2),
|
||
updated_at = NOW()
|
||
FROM falukant_data.character ch
|
||
WHERE ch.id = $1::int AND uh.user_id = ch.user_id;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_IMPRISONED_LOVER_AFF_SUB2: &str = r#"
|
||
UPDATE falukant_data.relationship_state rs
|
||
SET affection = GREATEST(0, COALESCE(rs.affection, 50) - 2),
|
||
updated_at = NOW()
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
WHERE rs.relationship_id = r.id AND rs.active = true
|
||
AND (r.character1_id = $1::int OR r.character2_id = $1::int);
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_VEHICLE_FOR_SEIZURE: &str = r#"
|
||
SELECT v.id AS vehicle_id,
|
||
COALESCE(v.condition, 100)::int AS veh_condition,
|
||
COALESCE(vt.cost, 0)::float8 AS type_cost
|
||
FROM falukant_data.vehicle v
|
||
JOIN falukant_type.vehicle vt ON vt.id = v.vehicle_type_id
|
||
WHERE v.falukant_user_id = $1::int
|
||
AND v.id NOT IN (
|
||
SELECT DISTINCT t.vehicle_id FROM falukant_data.transport t
|
||
WHERE t.vehicle_id IS NOT NULL
|
||
)
|
||
ORDER BY COALESCE(vt.cost, 0) ASC, v.id ASC
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_DELETE_VEHICLE: &str = r#"
|
||
DELETE FROM falukant_data.vehicle WHERE id = $1::int RETURNING id;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_UPDATE_PRISM_REMAINING: &str = r#"
|
||
UPDATE falukant_data.debtors_prism dp
|
||
SET remaining_debt = GREATEST(0, COALESCE(dp.remaining_debt, 0) - $2::float8),
|
||
assets_seized_json = COALESCE(assets_seized_json, '{}'::jsonb)
|
||
|| jsonb_build_object(
|
||
'last_vehicle',
|
||
jsonb_build_object('vehicle_id', $3::int, 'proceeds', $2::float8)
|
||
),
|
||
updated_at = NOW()
|
||
WHERE dp.character_id = $1::int AND dp.status = 'imprisoned'
|
||
RETURNING dp.remaining_debt;
|
||
"#;
|
||
|
||
pub const QUERY_DEBTORS_UPDATE_PRISM_REMAINING_MONEY: &str = r#"
|
||
UPDATE falukant_data.debtors_prism dp
|
||
SET remaining_debt = GREATEST(0, COALESCE(dp.remaining_debt, 0) - $2::float8),
|
||
assets_seized_json = COALESCE(assets_seized_json, '{}'::jsonb)
|
||
|| jsonb_build_object('last_cash_seizure', $2::float8),
|
||
updated_at = NOW()
|
||
WHERE dp.character_id = $1::int AND dp.status = 'imprisoned'
|
||
RETURNING dp.remaining_debt;
|
||
"#;
|
||
|
||
pub const QUERY_RANDOM_HEIR: &str = r#"
|
||
WITH chosen AS (
|
||
SELECT
|
||
cr.id AS relation_id,
|
||
cr.child_character_id
|
||
FROM
|
||
falukant_data.child_relation AS cr
|
||
JOIN
|
||
falukant_data.character AS ch
|
||
ON ch.id = cr.child_character_id
|
||
WHERE
|
||
(cr.father_character_id = $1 OR cr.mother_character_id = $1)
|
||
AND ch.region_id = (
|
||
SELECT region_id
|
||
FROM falukant_data.character
|
||
WHERE id = $1
|
||
)
|
||
AND ch.birthdate >= NOW() - INTERVAL '10 days'
|
||
AND ch.title_of_nobility = (
|
||
SELECT id
|
||
FROM falukant_type.title
|
||
WHERE label_tr = 'noncivil'
|
||
)
|
||
ORDER BY RANDOM()
|
||
LIMIT 1
|
||
)
|
||
UPDATE
|
||
falukant_data.child_relation AS cr2
|
||
SET
|
||
is_heir = TRUE,
|
||
updated_at = NOW()
|
||
FROM
|
||
chosen
|
||
WHERE
|
||
cr2.id = chosen.relation_id
|
||
RETURNING
|
||
chosen.child_character_id;
|
||
"#;
|
||
|
||
/// Aktualisiert den Geldstand eines Users. $1 = Geld als Text (z. B. "1234.56"), wird als numeric gecastet; $2 = user id.
|
||
/// Verwendung von Text-Parameter vermeidet "error serializing parameter 0" bei f64/numeric.
|
||
pub const QUERY_UPDATE_USER_MONEY: &str = r#"
|
||
UPDATE falukant_data.falukant_user
|
||
SET money = $1::numeric,
|
||
updated_at = NOW()
|
||
WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_FALUKANT_USER_ID: &str = r#"
|
||
SELECT user_id
|
||
FROM falukant_data.character
|
||
WHERE id = $1
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_AUTOBATISM: &str = r#"
|
||
UPDATE falukant_data.child_relation
|
||
SET name_set = TRUE
|
||
WHERE id IN (
|
||
SELECT cr.id
|
||
FROM falukant_data.child_relation cr
|
||
JOIN falukant_data.character c
|
||
ON c.id = cr.child_character_id
|
||
WHERE cr.name_set = FALSE
|
||
AND c.birthdate < current_date - INTERVAL '5 days'
|
||
);
|
||
"#;
|
||
|
||
// Biologische Fruchtbarkeit nach Alter der Frau (Jahres-Wahrscheinlichkeit prob_year).
|
||
// Konzeption: genau ein Wurf pro Ehe und Kalendertag (`UserCharacterWorker`), mit random() < prob_year.
|
||
// Entspricht „ein Spieljahr pro Kalendertag“: keine 24× stündlichen Versuche mehr, keine „Hochzeitsnacht“
|
||
// als Extra-Event — erste mögliche Konzeption am nächsten täglichen Fertilitätslauf nach der Hochzeit.
|
||
// Grenzen in Tagen: 1 Jahr ≈ 365 Tage (mother_age_days).
|
||
// Erstes gemeinsames Kind (0 Zeilen in child_relation für dieses Paar): prob_year mindestens 1.0
|
||
// im Alter 18–~44 (6570–16000 Tage), damit kinderlose Ehen nicht über viele Jahre ohne Nachwuchs bleiben.
|
||
// WICHTIG: Vater/Mutter und Alter immer über gender ableiten — nicht character1/2 fest als Mutter!
|
||
//
|
||
// Ablauf Ehe: (1) Konzeption setzt `marriage_pregnancy_due_at` (+5 Tage), (2) Geburt wenn fällig.
|
||
// Migration: `008_falukant_marriage_pregnancy_due.sql`.
|
||
|
||
/// Fällige Geburten (Ehe): `marriage_pregnancy_due_at <= NOW()`.
|
||
/// B2: keine Ehe-Geburt, solange die Mutter eine **geplante** Schwangerschaft (`character.pregnancy_due_at`) hat.
|
||
pub const QUERY_GET_MARRIAGE_BIRTH_DELIVERIES: &str = r#"
|
||
SELECT
|
||
r.id AS relationship_id,
|
||
CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END AS father_cid,
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END AS mother_cid,
|
||
CASE WHEN c1.gender = 'male' THEN c1.title_of_nobility ELSE c2.title_of_nobility END AS title_of_nobility,
|
||
CASE WHEN c1.gender = 'male' THEN c1.last_name ELSE c2.last_name END AS last_name,
|
||
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
|
||
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
|
||
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
|
||
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship r2
|
||
ON r2.id = r.relationship_type_id AND r2.tr = 'married'
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
JOIN falukant_data.character c_female ON c_female.id = (
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END
|
||
)
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE r.marriage_pregnancy_due_at IS NOT NULL
|
||
AND r.marriage_pregnancy_due_at <= NOW()
|
||
AND ((c1.gender = 'male' AND c2.gender = 'female')
|
||
OR (c1.gender = 'female' AND c2.gender = 'male'))
|
||
AND c_female.pregnancy_due_at IS NULL;
|
||
"#;
|
||
|
||
pub const QUERY_CLEAR_MARRIAGE_PREGNANCY_DUE: &str = r#"
|
||
UPDATE falukant_data.relationship
|
||
SET marriage_pregnancy_due_at = NULL
|
||
WHERE id = $1::int;
|
||
"#;
|
||
|
||
/// Hängengebliebene Ehe-Schwangerschaft (Geburt fehlgeschlagen / inkonsistent): Flag nach 30 Tagen löschen.
|
||
pub const QUERY_CLEAR_STALE_MARRIAGE_PREGNANCY_DUE: &str = r#"
|
||
UPDATE falukant_data.relationship
|
||
SET marriage_pregnancy_due_at = NULL
|
||
WHERE marriage_pregnancy_due_at IS NOT NULL
|
||
AND marriage_pregnancy_due_at < NOW() - INTERVAL '30 days';
|
||
"#;
|
||
|
||
/// Täglicher Konzeptionswurf (Ehe): bei Treffer wird `marriage_pregnancy_due_at` auf +5 Tage gesetzt.
|
||
pub const QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE: &str = r#"
|
||
WITH paired AS (
|
||
SELECT
|
||
r.id AS relationship_id,
|
||
CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END AS father_cid,
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END AS mother_cid,
|
||
CASE WHEN c1.gender = 'male' THEN c1.title_of_nobility ELSE c2.title_of_nobility END AS title_of_nobility,
|
||
CASE WHEN c1.gender = 'male' THEN c1.last_name ELSE c2.last_name END AS last_name,
|
||
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
|
||
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
|
||
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
|
||
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days,
|
||
(
|
||
SELECT COUNT(*)::int
|
||
FROM falukant_data.child_relation cr
|
||
WHERE cr.father_character_id = (CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END)
|
||
AND cr.mother_character_id = (CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END)
|
||
) AS shared_children_count
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship r2
|
||
ON r2.id = r.relationship_type_id AND r2.tr = 'married'
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
JOIN falukant_data.character c_female ON c_female.id = (
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END
|
||
)
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE r.marriage_pregnancy_due_at IS NULL
|
||
AND ((c1.gender = 'male' AND c2.gender = 'female')
|
||
OR (c1.gender = 'female' AND c2.gender = 'male'))
|
||
AND c_female.pregnancy_due_at IS NULL
|
||
),
|
||
mother_age AS (
|
||
SELECT
|
||
p.relationship_id,
|
||
p.father_cid,
|
||
p.mother_cid,
|
||
p.title_of_nobility,
|
||
p.last_name,
|
||
p.region_id,
|
||
p.father_uid,
|
||
p.mother_uid,
|
||
p.mother_age_days,
|
||
p.shared_children_count,
|
||
CASE
|
||
WHEN p.mother_age_days < 4380 THEN 0.005
|
||
WHEN p.mother_age_days < 4745 THEN 0.30
|
||
WHEN p.mother_age_days < 5110 THEN 0.45
|
||
WHEN p.mother_age_days < 5475 THEN 0.55
|
||
WHEN p.mother_age_days < 5840 THEN 0.60
|
||
WHEN p.mother_age_days < 6205 THEN 0.725
|
||
WHEN p.mother_age_days < 6570 THEN 0.80
|
||
WHEN p.mother_age_days < 7305 THEN 0.855
|
||
WHEN p.mother_age_days < 9125 THEN 0.875
|
||
WHEN p.mother_age_days < 10950 THEN 0.84
|
||
WHEN p.mother_age_days < 11315 THEN 0.785
|
||
WHEN p.mother_age_days < 11680 THEN 0.765
|
||
WHEN p.mother_age_days < 12045 THEN 0.74
|
||
WHEN p.mother_age_days < 12410 THEN 0.72
|
||
WHEN p.mother_age_days < 12775 THEN 0.695
|
||
WHEN p.mother_age_days < 13140 THEN 0.65
|
||
WHEN p.mother_age_days < 13505 THEN 0.63
|
||
WHEN p.mother_age_days < 13870 THEN 0.60
|
||
WHEN p.mother_age_days < 14235 THEN 0.55
|
||
WHEN p.mother_age_days < 14600 THEN 0.50
|
||
WHEN p.mother_age_days < 14965 THEN 0.45
|
||
WHEN p.mother_age_days < 15330 THEN 0.35
|
||
WHEN p.mother_age_days < 15695 THEN 0.25
|
||
WHEN p.mother_age_days < 16060 THEN 0.15
|
||
WHEN p.mother_age_days < 16425 THEN 0.075
|
||
WHEN p.mother_age_days < 16790 THEN 0.03
|
||
WHEN p.mother_age_days < 17155 THEN 0.02
|
||
WHEN p.mother_age_days < 17520 THEN 0.015
|
||
WHEN p.mother_age_days < 18250 THEN 0.005
|
||
ELSE 0.001
|
||
END AS prob_year_raw
|
||
FROM paired p
|
||
),
|
||
mother_age_final AS (
|
||
SELECT
|
||
ma.*,
|
||
CASE
|
||
WHEN ma.shared_children_count = 0
|
||
AND ma.mother_age_days >= 6570
|
||
AND ma.mother_age_days < 16000
|
||
THEN GREATEST(ma.prob_year_raw, 1.0)
|
||
ELSE ma.prob_year_raw
|
||
END AS prob_year
|
||
FROM mother_age ma
|
||
),
|
||
conceivable AS (
|
||
SELECT ma.relationship_id
|
||
FROM mother_age_final ma
|
||
WHERE ma.mother_age_days >= 4380
|
||
AND ma.mother_age_days < 18993
|
||
AND random() < ma.prob_year
|
||
)
|
||
UPDATE falukant_data.relationship r
|
||
SET marriage_pregnancy_due_at = NOW() + INTERVAL '5 days'
|
||
FROM conceivable c
|
||
WHERE r.id = c.relationship_id;
|
||
"#;
|
||
|
||
pub const QUERY_MARRIAGE_PREGNANCY_COLUMN_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'relationship'
|
||
AND column_name = 'marriage_pregnancy_due_at'
|
||
) AS ready;
|
||
"#;
|
||
|
||
/// Ehe: ohne Migration 008 — täglicher Wurf legt sofort ein Kind an (Daemon ruft 1×/Kalendertag auf).
|
||
pub const QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES: &str = r#"
|
||
WITH paired AS (
|
||
SELECT
|
||
CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END AS father_cid,
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END AS mother_cid,
|
||
CASE WHEN c1.gender = 'male' THEN c1.title_of_nobility ELSE c2.title_of_nobility END AS title_of_nobility,
|
||
CASE WHEN c1.gender = 'male' THEN c1.last_name ELSE c2.last_name END AS last_name,
|
||
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
|
||
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
|
||
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
|
||
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days,
|
||
(
|
||
SELECT COUNT(*)::int
|
||
FROM falukant_data.child_relation cr
|
||
WHERE cr.father_character_id = (CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END)
|
||
AND cr.mother_character_id = (CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END)
|
||
) AS shared_children_count
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship r2
|
||
ON r2.id = r.relationship_type_id AND r2.tr = 'married'
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
JOIN falukant_data.character c_female ON c_female.id = (
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END
|
||
)
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE ((c1.gender = 'male' AND c2.gender = 'female')
|
||
OR (c1.gender = 'female' AND c2.gender = 'male'))
|
||
AND c_female.pregnancy_due_at IS NULL
|
||
),
|
||
mother_age AS (
|
||
SELECT
|
||
p.father_cid,
|
||
p.mother_cid,
|
||
p.title_of_nobility,
|
||
p.last_name,
|
||
p.region_id,
|
||
p.father_uid,
|
||
p.mother_uid,
|
||
p.mother_age_days,
|
||
p.shared_children_count,
|
||
CASE
|
||
WHEN p.mother_age_days < 4380 THEN 0.005
|
||
WHEN p.mother_age_days < 4745 THEN 0.30
|
||
WHEN p.mother_age_days < 5110 THEN 0.45
|
||
WHEN p.mother_age_days < 5475 THEN 0.55
|
||
WHEN p.mother_age_days < 5840 THEN 0.60
|
||
WHEN p.mother_age_days < 6205 THEN 0.725
|
||
WHEN p.mother_age_days < 6570 THEN 0.80
|
||
WHEN p.mother_age_days < 7305 THEN 0.855
|
||
WHEN p.mother_age_days < 9125 THEN 0.875
|
||
WHEN p.mother_age_days < 10950 THEN 0.84
|
||
WHEN p.mother_age_days < 11315 THEN 0.785
|
||
WHEN p.mother_age_days < 11680 THEN 0.765
|
||
WHEN p.mother_age_days < 12045 THEN 0.74
|
||
WHEN p.mother_age_days < 12410 THEN 0.72
|
||
WHEN p.mother_age_days < 12775 THEN 0.695
|
||
WHEN p.mother_age_days < 13140 THEN 0.65
|
||
WHEN p.mother_age_days < 13505 THEN 0.63
|
||
WHEN p.mother_age_days < 13870 THEN 0.60
|
||
WHEN p.mother_age_days < 14235 THEN 0.55
|
||
WHEN p.mother_age_days < 14600 THEN 0.50
|
||
WHEN p.mother_age_days < 14965 THEN 0.45
|
||
WHEN p.mother_age_days < 15330 THEN 0.35
|
||
WHEN p.mother_age_days < 15695 THEN 0.25
|
||
WHEN p.mother_age_days < 16060 THEN 0.15
|
||
WHEN p.mother_age_days < 16425 THEN 0.075
|
||
WHEN p.mother_age_days < 16790 THEN 0.03
|
||
WHEN p.mother_age_days < 17155 THEN 0.02
|
||
WHEN p.mother_age_days < 17520 THEN 0.015
|
||
WHEN p.mother_age_days < 18250 THEN 0.005
|
||
ELSE 0.001
|
||
END AS prob_year_raw
|
||
FROM paired p
|
||
),
|
||
mother_age_final AS (
|
||
SELECT
|
||
ma.*,
|
||
CASE
|
||
WHEN ma.shared_children_count = 0
|
||
AND ma.mother_age_days >= 6570
|
||
AND ma.mother_age_days < 16000
|
||
THEN GREATEST(ma.prob_year_raw, 1.0)
|
||
ELSE ma.prob_year_raw
|
||
END AS prob_year
|
||
FROM mother_age ma
|
||
)
|
||
SELECT
|
||
father_cid,
|
||
mother_cid,
|
||
title_of_nobility,
|
||
last_name,
|
||
region_id,
|
||
father_uid,
|
||
mother_uid,
|
||
mother_age_days,
|
||
prob_year * 100.0 AS prob_pct
|
||
FROM mother_age_final
|
||
WHERE mother_age_days >= 4380
|
||
AND mother_age_days < 18993
|
||
AND random() < prob_year;
|
||
"#;
|
||
|
||
/// Spalten `pregnancy_due_at` / `pregnancy_father_character_id` auf `character` (Migration `011_…`).
|
||
pub const QUERY_CHARACTER_PLANNED_PREGNANCY_COLUMNS_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'character'
|
||
AND column_name = 'pregnancy_due_at'
|
||
) AS ready;
|
||
"#;
|
||
|
||
/// Fällige **geplante** Geburten (Weg A): Mutter trägt Termin + Vater; kein Ehe-`relationship`-Bezug.
|
||
/// Vater NULL oder = Mutter: keine Zeile (Daemon überspringt; optional im Log).
|
||
pub const QUERY_GET_PLANNED_CHARACTER_BIRTH_DELIVERIES: &str = r#"
|
||
SELECT
|
||
c_m.id AS mother_cid,
|
||
c_f.id AS father_cid,
|
||
CASE WHEN c_f.gender = 'male' THEN c_f.title_of_nobility ELSE c_m.title_of_nobility END AS title_of_nobility,
|
||
CASE WHEN c_f.gender = 'male' THEN c_f.last_name ELSE c_m.last_name END AS last_name,
|
||
CASE WHEN c_f.gender = 'male' THEN c_f.region_id ELSE c_m.region_id END AS region_id,
|
||
fu_f.id AS father_uid,
|
||
fu_m.id AS mother_uid,
|
||
CASE
|
||
WHEN EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
WHERE (r.character1_id = c_f.id AND r.character2_id = c_m.id)
|
||
OR (r.character1_id = c_m.id AND r.character2_id = c_f.id)
|
||
) THEN 'lover'
|
||
ELSE 'marriage'
|
||
END AS birth_context
|
||
FROM falukant_data.character c_m
|
||
JOIN falukant_data.character c_f ON c_f.id = c_m.pregnancy_father_character_id
|
||
LEFT JOIN falukant_data.falukant_user fu_m ON fu_m.id = c_m.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu_f ON fu_f.id = c_f.user_id
|
||
WHERE c_m.pregnancy_due_at IS NOT NULL
|
||
AND c_m.pregnancy_due_at <= NOW()
|
||
AND c_m.pregnancy_father_character_id IS NOT NULL
|
||
AND c_m.pregnancy_father_character_id <> c_m.id
|
||
AND c_m.health > 0;
|
||
"#;
|
||
|
||
pub const QUERY_CLEAR_CHARACTER_PREGNANCY_AFTER_BIRTH: &str = r#"
|
||
UPDATE falukant_data.character
|
||
SET pregnancy_due_at = NULL,
|
||
pregnancy_father_character_id = NULL,
|
||
updated_at = NOW()
|
||
WHERE id = $1::int;
|
||
"#;
|
||
|
||
/// Wie Node-Admin: `birth_context` / `legitimacy` / `public_known` je nach Liebschaft vs. Ehe.
|
||
pub const QUERY_INSERT_CHILD_RELATION_PLANNED_BIRTH: &str = r#"
|
||
INSERT INTO falukant_data.child_relation (
|
||
father_character_id,
|
||
mother_character_id,
|
||
child_character_id,
|
||
name_set,
|
||
legitimacy,
|
||
birth_context,
|
||
public_known,
|
||
created_at,
|
||
updated_at
|
||
)
|
||
VALUES (
|
||
$1::int,
|
||
$2::int,
|
||
$3::int,
|
||
FALSE,
|
||
CASE WHEN $4::text = 'lover' THEN 'hidden_bastard'::varchar ELSE 'legitimate'::varchar END,
|
||
CASE WHEN $4::text = 'lover' THEN 'lover'::varchar ELSE 'marriage'::varchar END,
|
||
CASE WHEN $4::text = 'lover' THEN FALSE ELSE TRUE END,
|
||
NOW(),
|
||
NOW()
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_CHILD: &str = r#"
|
||
INSERT INTO falukant_data.character (
|
||
user_id,
|
||
region_id,
|
||
first_name,
|
||
last_name,
|
||
birthdate,
|
||
gender,
|
||
title_of_nobility,
|
||
mood_id,
|
||
created_at,
|
||
updated_at
|
||
) VALUES (
|
||
NULL,
|
||
$1::int,
|
||
(
|
||
SELECT id
|
||
FROM falukant_predefine.firstname
|
||
WHERE gender = $2
|
||
ORDER BY RANDOM()
|
||
LIMIT 1
|
||
),
|
||
$3::int,
|
||
NOW(),
|
||
$2::varchar,
|
||
$4::int,
|
||
(
|
||
SELECT id
|
||
FROM falukant_type.mood
|
||
ORDER BY RANDOM()
|
||
LIMIT 1
|
||
),
|
||
NOW(),
|
||
NOW()
|
||
)
|
||
RETURNING id AS child_cid;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_CHILD_RELATION: &str = r#"
|
||
INSERT INTO falukant_data.child_relation (
|
||
father_character_id,
|
||
mother_character_id,
|
||
child_character_id,
|
||
name_set,
|
||
created_at,
|
||
updated_at
|
||
)
|
||
VALUES (
|
||
$1::int,
|
||
$2::int,
|
||
$3::int,
|
||
FALSE,
|
||
NOW(), NOW()
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_KNOWLEDGE: &str = r#"
|
||
DELETE FROM falukant_data.knowledge
|
||
WHERE character_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_DEBTORS_PRISM: &str = r#"
|
||
DELETE FROM falukant_data.debtors_prism
|
||
WHERE character_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_POLITICAL_OFFICE: &str = r#"
|
||
WITH removed AS (
|
||
DELETE FROM falukant_data.political_office
|
||
WHERE character_id = $1
|
||
RETURNING office_type_id, region_id
|
||
),
|
||
affected AS (
|
||
SELECT DISTINCT office_type_id, region_id
|
||
FROM removed
|
||
),
|
||
seats AS (
|
||
SELECT
|
||
pot.id AS office_type_id,
|
||
rt.id AS region_id,
|
||
pot.seats_per_region AS seats_total
|
||
FROM falukant_type.political_office_type AS pot
|
||
JOIN falukant_type.region AS rt
|
||
ON pot.region_type = rt.label_tr
|
||
JOIN affected AS a
|
||
ON a.office_type_id = pot.id
|
||
AND a.region_id = rt.id
|
||
),
|
||
ranked AS (
|
||
SELECT
|
||
po.id,
|
||
po.office_type_id,
|
||
po.region_id,
|
||
s.seats_total,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY po.office_type_id, po.region_id
|
||
ORDER BY po.created_at DESC
|
||
) AS rn
|
||
FROM falukant_data.political_office AS po
|
||
JOIN seats AS s
|
||
ON s.office_type_id = po.office_type_id
|
||
AND s.region_id = po.region_id
|
||
),
|
||
to_delete AS (
|
||
SELECT id
|
||
FROM ranked
|
||
WHERE rn > seats_total
|
||
)
|
||
DELETE FROM falukant_data.political_office
|
||
WHERE id IN (SELECT id FROM to_delete);
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_ELECTION_CANDIDATE: &str = r#"
|
||
DELETE FROM falukant_data.election_candidate
|
||
WHERE character_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_STOCK_TYPE_ID: &str = r#"
|
||
SELECT id FROM falukant_type.stock WHERE label_tr = $1 LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_INVENTORY_ITEMS: &str = r#"
|
||
SELECT i.id AS inventory_id, i.quantity AS inventory_quantity, i.stock_id FROM falukant_data.inventory i JOIN falukant_data.stock s ON i.stock_id = s.id JOIN falukant_data.branch b ON s.branch_id = b.id WHERE b.region_id = $1 AND s.stock_type_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_REDUCE_INVENTORY: &str = r#"
|
||
UPDATE falukant_data.inventory SET quantity = $1 WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_INVENTORY: &str = r#"
|
||
DELETE FROM falukant_data.inventory WHERE stock_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_STOCK: &str = r#"
|
||
DELETE FROM falukant_data.stock WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_STOCK_INVENTORY: &str = r#"
|
||
SELECT id, quantity FROM falukant_data.inventory WHERE stock_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_CAP_INVENTORY: &str = r#"
|
||
UPDATE falukant_data.inventory SET quantity = $1 WHERE id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_GET_USER_INVENTORY_ITEMS: &str = r#"
|
||
SELECT i.id AS inventory_id, i.quantity AS inventory_quantity, i.stock_id
|
||
FROM falukant_data.inventory i
|
||
JOIN falukant_data.stock s ON i.stock_id = s.id
|
||
JOIN falukant_data.branch b ON s.branch_id = b.id
|
||
WHERE b.falukant_user_id = $1 AND s.stock_type_id = $2;
|
||
"#;
|
||
// Produce worker queries
|
||
pub const QUERY_GET_FINISHED_PRODUCTIONS: &str = r#"
|
||
SELECT DISTINCT ON (p.id)
|
||
p.id AS production_id,
|
||
p.branch_id,
|
||
p.product_id,
|
||
p.quantity,
|
||
p.start_timestamp,
|
||
pr.production_time,
|
||
br.region_id,
|
||
br.falukant_user_id AS user_id,
|
||
ROUND(
|
||
GREATEST(
|
||
0,
|
||
LEAST(
|
||
100,
|
||
(
|
||
(COALESCE(k.knowledge, 0) * 0.75
|
||
+ COALESCE(k2.knowledge, 0) * 0.25)
|
||
* COALESCE(pwe.quality_effect, 100) / 100.0
|
||
)
|
||
)
|
||
)
|
||
)::int AS quality
|
||
FROM falukant_data.production p
|
||
JOIN falukant_type.product pr
|
||
ON p.product_id = pr.id
|
||
JOIN falukant_data.branch br
|
||
ON p.branch_id = br.id
|
||
LEFT JOIN falukant_data.character c
|
||
ON c.user_id = br.falukant_user_id
|
||
LEFT JOIN falukant_data.knowledge k
|
||
ON p.product_id = k.product_id
|
||
AND k.character_id = c.id
|
||
LEFT JOIN falukant_data.director d
|
||
ON d.employer_user_id = br.falukant_user_id
|
||
LEFT JOIN falukant_data.knowledge k2
|
||
ON k2.character_id = d.director_character_id
|
||
AND k2.product_id = p.product_id
|
||
LEFT JOIN falukant_data.weather w
|
||
ON w.region_id = br.region_id
|
||
LEFT JOIN falukant_type.product_weather_effect pwe
|
||
ON pwe.product_id = p.product_id
|
||
AND pwe.weather_type_id = w.weather_type_id
|
||
-- Prüfe, ob genug freier Lagerplatz vorhanden ist (korrelierte Subqueries, keine JOIN-Multiplikation)
|
||
LEFT JOIN (
|
||
SELECT
|
||
br2.id AS branch_id,
|
||
(SELECT COALESCE(SUM(quantity), 0) FROM falukant_data.stock WHERE branch_id = br2.id) AS stock_size,
|
||
(SELECT COALESCE(SUM(fdi.quantity), 0) FROM falukant_data.stock fds
|
||
JOIN falukant_data.inventory fdi ON fdi.stock_id = fds.id WHERE fds.branch_id = br2.id) AS used_in_stock,
|
||
(SELECT COALESCE(SUM(quantity), 0) FROM falukant_data.production WHERE branch_id = br2.id) AS running_productions_quantity
|
||
FROM falukant_data.branch br2
|
||
) capacity ON capacity.branch_id = p.branch_id
|
||
-- Wetter-Effekte derzeit aus der Qualitätsberechnung entfernt
|
||
WHERE p.start_timestamp + INTERVAL '1 minute' * pr.production_time <= NOW()
|
||
-- Freier Platz = stock_size - used_in_stock; laufende Produktionen belegen noch keinen Platz.
|
||
-- Es muss genug Platz für p.quantity sein: (stock_size - used_in_stock) >= p.quantity
|
||
AND (capacity.stock_size - capacity.used_in_stock) >= p.quantity
|
||
ORDER BY p.id, p.start_timestamp;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_PRODUCTION: &str = r#"
|
||
DELETE FROM falukant_data.production
|
||
WHERE id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_UPDATE_PRODUCTION_LOG: &str = r#"
|
||
INSERT INTO falukant_log.production (
|
||
region_id,
|
||
product_id,
|
||
quantity,
|
||
producer_id,
|
||
production_date
|
||
) VALUES ($1, $2, $3, $4, CURRENT_DATE)
|
||
ON CONFLICT (producer_id, product_id, region_id, production_date)
|
||
DO UPDATE
|
||
SET quantity = falukant_log.production.quantity + EXCLUDED.quantity;
|
||
"#;
|
||
|
||
pub const QUERY_ADD_OVERPRODUCTION_NOTIFICATION: &str = r#"
|
||
INSERT INTO falukant_log.notification (
|
||
user_id,
|
||
tr,
|
||
shown,
|
||
created_at,
|
||
updated_at
|
||
) VALUES ($1, $2, FALSE, NOW(), NOW());
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_OVERPRODUCTION_NOTIFICATION: &str = r#"
|
||
UPDATE falukant_log.notification
|
||
SET tr = jsonb_set(
|
||
tr::jsonb,
|
||
'{value}',
|
||
to_jsonb(COALESCE((tr::jsonb->>'value')::int, 0) + $3)
|
||
)::text,
|
||
updated_at = NOW()
|
||
WHERE user_id = $1
|
||
AND shown = FALSE
|
||
AND tr::text LIKE '%"tr":"production.overproduction"%'
|
||
AND (tr::jsonb->>'branch_id')::int = $2;
|
||
"#;
|
||
|
||
pub const QUERY_FIND_OVERPRODUCTION_NOTIFICATION: &str = r#"
|
||
SELECT id, tr
|
||
FROM falukant_log.notification
|
||
WHERE user_id = $1
|
||
AND shown = FALSE
|
||
AND tr::text LIKE '%"tr":"production.overproduction"%'
|
||
AND (tr::jsonb->>'branch_id')::int = $2
|
||
ORDER BY created_at DESC
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
// Aliases for personal variants (keeps original prepared statement names used in events.worker)
|
||
pub const QUERY_REDUCE_INVENTORY_PERSONAL: &str = QUERY_REDUCE_INVENTORY;
|
||
pub const QUERY_DELETE_INVENTORY_PERSONAL: &str = QUERY_DELETE_INVENTORY;
|
||
pub const QUERY_DELETE_STOCK_PERSONAL: &str = QUERY_DELETE_STOCK;
|
||
pub const QUERY_GET_STOCK_INVENTORY_PERSONAL: &str = QUERY_GET_STOCK_INVENTORY;
|
||
pub const QUERY_CAP_INVENTORY_PERSONAL: &str = QUERY_CAP_INVENTORY;
|
||
|
||
// value_recalculation worker queries
|
||
pub const QUERY_UPDATE_PRODUCT_KNOWLEDGE_USER: &str = r#"
|
||
UPDATE falukant_data.knowledge k
|
||
SET knowledge = LEAST(100, k.knowledge + 1)
|
||
FROM falukant_data.character c
|
||
JOIN falukant_log.production p
|
||
ON DATE(p.production_timestamp) = CURRENT_DATE - INTERVAL '1 day'
|
||
WHERE c.id = k.character_id
|
||
AND c.user_id = 18
|
||
AND k.product_id = 10;
|
||
"#;
|
||
|
||
/// Aufräumen alter Log-Zeilen (Speicher); Zertifikats-Mindest-Produktionen seit Aufstieg siehe
|
||
/// `certificate_productions_count_since` — kein „Reset“ über Löschen nötig.
|
||
pub const QUERY_DELETE_OLD_PRODUCTIONS: &str = r#"
|
||
DELETE FROM falukant_log.production flp
|
||
WHERE DATE(flp.production_timestamp) < CURRENT_DATE - INTERVAL '30 days';
|
||
"#;
|
||
|
||
pub const QUERY_GET_PRODUCERS_LAST_DAY: &str = r#"
|
||
SELECT p.producer_id
|
||
FROM falukant_log.production p
|
||
WHERE DATE(p.production_timestamp) = CURRENT_DATE - INTERVAL '1 day'
|
||
GROUP BY producer_id;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_REGION_SELL_PRICE: &str = r#"
|
||
UPDATE falukant_data.town_product_worth tpw
|
||
SET worth_percent =
|
||
GREATEST(
|
||
0,
|
||
LEAST(
|
||
CASE
|
||
WHEN s.quantity > COALESCE(s.avg_sells, 0) * 1.05 THEN tpw.worth_percent + 1
|
||
WHEN s.quantity < COALESCE(s.avg_sells, 0) * 0.95 THEN tpw.worth_percent - 1
|
||
ELSE tpw.worth_percent
|
||
END,
|
||
100
|
||
)
|
||
)
|
||
FROM (
|
||
SELECT region_id,
|
||
product_id,
|
||
quantity,
|
||
(SELECT AVG(quantity)
|
||
FROM falukant_log.sell avs
|
||
WHERE avs.product_id = s.product_id) AS avg_sells
|
||
FROM falukant_log.sell s
|
||
WHERE DATE(s.sell_timestamp) = CURRENT_DATE - INTERVAL '1 day'
|
||
) s
|
||
WHERE tpw.region_id = s.region_id
|
||
AND tpw.product_id = s.product_id;
|
||
"#;
|
||
|
||
pub const QUERY_DELETE_REGION_SELL_PRICE: &str = r#"
|
||
DELETE FROM falukant_log.sell s
|
||
WHERE DATE(s.sell_timestamp) < CURRENT_DATE;
|
||
"#;
|
||
|
||
pub const QUERY_GET_SELL_REGIONS: &str = r#"
|
||
SELECT s.region_id
|
||
FROM falukant_log.sell s
|
||
WHERE DATE(s.sell_timestamp) = CURRENT_DATE - INTERVAL '1 day'
|
||
GROUP BY region_id;
|
||
"#;
|
||
|
||
pub const QUERY_HOURLY_PRICE_RECALCULATION: &str = r#"
|
||
WITH city_sales AS (
|
||
SELECT s.region_id, s.product_id, SUM(s.quantity) AS total_sold
|
||
FROM falukant_log.sell s
|
||
WHERE s.sell_timestamp >= NOW() - INTERVAL '1 hour'
|
||
GROUP BY s.region_id, s.product_id
|
||
),
|
||
world_avg AS (
|
||
SELECT product_id, AVG(total_sold) AS avg_sold
|
||
FROM city_sales
|
||
GROUP BY product_id
|
||
),
|
||
adjustments AS (
|
||
SELECT
|
||
cs.region_id,
|
||
cs.product_id,
|
||
CASE
|
||
WHEN cs.total_sold > COALESCE(wa.avg_sold, 0) * 1.05 THEN 1.10
|
||
WHEN cs.total_sold < COALESCE(wa.avg_sold, 0) * 0.95 THEN 0.90
|
||
ELSE 1.00
|
||
END AS factor
|
||
FROM city_sales cs
|
||
JOIN world_avg wa ON wa.product_id = cs.product_id
|
||
)
|
||
UPDATE falukant_data.town_product_worth tpw
|
||
SET worth_percent = GREATEST(0, LEAST(100, tpw.worth_percent * adj.factor))
|
||
FROM adjustments adj
|
||
WHERE tpw.region_id = adj.region_id
|
||
AND tpw.product_id = adj.product_id;
|
||
"#;
|
||
|
||
/// Fügt für eine Region Preise in die Historie ein.
|
||
/// Berechnet den regionalen Produktpreis als sell_cost * worth_percent / 100.
|
||
pub const QUERY_INSERT_PRODUCT_PRICE_HISTORY: &str = r#"
|
||
INSERT INTO falukant_log.product_price_history (product_id, region_id, price, recorded_at)
|
||
SELECT
|
||
tpw.product_id,
|
||
tpw.region_id,
|
||
ROUND(ftp.sell_cost * tpw.worth_percent / 100.0, 2) AS price,
|
||
NOW() AS recorded_at
|
||
FROM falukant_data.town_product_worth tpw
|
||
JOIN falukant_type.product ftp ON ftp.id = tpw.product_id
|
||
WHERE tpw.region_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_SET_MARRIAGES_BY_PARTY: &str = r#"
|
||
WITH updated_relations AS (
|
||
UPDATE falukant_data.relationship AS rel
|
||
SET relationship_type_id = (
|
||
SELECT id
|
||
FROM falukant_type.relationship AS rt
|
||
WHERE rt.tr = 'married'
|
||
),
|
||
marriage_satisfaction = 55,
|
||
marriage_drift_high = 0,
|
||
marriage_drift_low = 0
|
||
WHERE rel.id IN (
|
||
SELECT rel2.id
|
||
FROM falukant_data.party AS p
|
||
JOIN falukant_type.party AS pt
|
||
ON pt.id = p.party_type_id
|
||
AND pt.tr = 'wedding'
|
||
JOIN falukant_data.falukant_user AS fu
|
||
ON fu.id = p.falukant_user_id
|
||
JOIN falukant_data.character AS c
|
||
ON c.user_id = fu.id
|
||
JOIN falukant_data.relationship AS rel2
|
||
ON rel2.character1_id = c.id
|
||
OR rel2.character2_id = c.id
|
||
JOIN falukant_type.relationship AS rt2
|
||
ON rt2.id = rel2.relationship_type_id
|
||
AND rt2.tr = 'engaged'
|
||
WHERE p.created_at <= NOW() - INTERVAL '1 day'
|
||
)
|
||
RETURNING character1_id, character2_id
|
||
)
|
||
SELECT
|
||
c1.user_id AS character1_user,
|
||
c2.user_id AS character2_user
|
||
FROM updated_relations AS ur
|
||
JOIN falukant_data.character AS c1
|
||
ON c1.id = ur.character1_id
|
||
JOIN falukant_data.character AS c2
|
||
ON c2.id = ur.character2_id;
|
||
"#;
|
||
|
||
pub const QUERY_GET_STUDYINGS_TO_EXECUTE: &str = r#"
|
||
SELECT
|
||
l.id,
|
||
l.associated_falukant_user_id,
|
||
l.associated_learning_character_id,
|
||
l.learn_all_products,
|
||
l.learning_recipient_id,
|
||
l.product_id,
|
||
lr.tr
|
||
FROM falukant_data.learning l
|
||
JOIN falukant_type.learn_recipient lr
|
||
ON lr.id = l.learning_recipient_id
|
||
WHERE l.learning_is_executed = FALSE
|
||
AND l.created_at + INTERVAL '1 day' < NOW();
|
||
"#;
|
||
|
||
pub const QUERY_GET_OWN_CHARACTER_ID: &str = r#"
|
||
SELECT id
|
||
FROM falukant_data.character c
|
||
WHERE c.user_id = $1;
|
||
"#;
|
||
|
||
pub const QUERY_INCREASE_ONE_PRODUCT_KNOWLEDGE: &str = r#"
|
||
UPDATE falukant_data.knowledge k
|
||
SET knowledge = LEAST(100, k.knowledge + $1)
|
||
WHERE k.character_id = $2
|
||
AND k.product_id = $3;
|
||
"#;
|
||
|
||
pub const QUERY_INCREASE_ALL_PRODUCTS_KNOWLEDGE: &str = r#"
|
||
UPDATE falukant_data.knowledge k
|
||
SET knowledge = LEAST(100, k.knowledge + $1)
|
||
WHERE k.character_id = $2;
|
||
"#;
|
||
|
||
pub const QUERY_SET_LEARNING_DONE: &str = r#"
|
||
UPDATE falukant_data.learning
|
||
SET learning_is_executed = TRUE,
|
||
updated_at = NOW()
|
||
WHERE id = $1;
|
||
"#;
|
||
|
||
// Church Office Queries (siehe docs/FALUKANT_CHURCH_DAEMON.md)
|
||
pub const QUERY_FIND_AVAILABLE_CHURCH_OFFICES: &str = r#"
|
||
SELECT
|
||
cot.id AS office_type_id,
|
||
cot.name AS office_type_name,
|
||
cot.hierarchy_level,
|
||
cot.seats_per_region,
|
||
cot.region_type,
|
||
r.id AS region_id,
|
||
COUNT(co.id) AS occupied_seats
|
||
FROM falukant_type.church_office_type cot
|
||
CROSS JOIN falukant_data.region r
|
||
JOIN falukant_type.region tr ON r.region_type_id = tr.id
|
||
LEFT JOIN falukant_data.church_office co
|
||
ON cot.id = co.office_type_id
|
||
AND co.region_id = r.id
|
||
WHERE tr.label_tr = cot.region_type
|
||
GROUP BY cot.id, cot.name, cot.hierarchy_level, cot.seats_per_region, cot.region_type, r.id
|
||
HAVING COUNT(co.id) < cot.seats_per_region
|
||
ORDER BY cot.hierarchy_level ASC, r.id;
|
||
"#;
|
||
|
||
pub const QUERY_FIND_CHURCH_SUPERVISOR: &str = r#"
|
||
SELECT
|
||
co.id AS office_id,
|
||
co.character_id AS supervisor_character_id,
|
||
co.region_id,
|
||
cot.hierarchy_level
|
||
FROM falukant_data.church_office co
|
||
JOIN falukant_type.church_office_type cot ON co.office_type_id = cot.id
|
||
WHERE co.region_id = $1
|
||
AND cot.hierarchy_level > (
|
||
SELECT hierarchy_level
|
||
FROM falukant_type.church_office_type
|
||
WHERE id = $2
|
||
)
|
||
ORDER BY cot.hierarchy_level ASC
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_GET_CHURCH_OFFICE_REQUIREMENTS: &str = r#"
|
||
SELECT
|
||
id,
|
||
office_type_id,
|
||
prerequisite_office_type_id,
|
||
min_title_level
|
||
FROM falukant_predefine.church_office_requirement
|
||
WHERE office_type_id = $1;
|
||
"#;
|
||
|
||
/// Optional für Backend/API; der Daemon nutzt `QUERY_GET_PENDING_CHURCH_APPLICATIONS_FOR_SCORING`.
|
||
#[allow(dead_code)]
|
||
pub const QUERY_GET_PENDING_CHURCH_APPLICATIONS: &str = r#"
|
||
SELECT
|
||
ca.id AS application_id,
|
||
ca.office_type_id,
|
||
ca.character_id AS applicant_character_id,
|
||
ca.region_id,
|
||
ca.supervisor_id,
|
||
cot.name AS office_type_name,
|
||
cot.hierarchy_level
|
||
FROM falukant_data.church_application ca
|
||
JOIN falukant_type.church_office_type cot ON ca.office_type_id = cot.id
|
||
WHERE ca.status = 'pending'
|
||
AND ca.supervisor_id = $1
|
||
ORDER BY cot.hierarchy_level ASC, ca.created_at ASC;
|
||
"#;
|
||
|
||
/// Voraussetzung: Migration `007_falukant_character_church_career.sql` (highest_church_hierarchy_ever).
|
||
pub const QUERY_CHECK_CHARACTER_ELIGIBILITY: &str = r#"
|
||
WITH prereq AS (
|
||
SELECT $2::int AS prereq_type_id,
|
||
CASE WHEN $2::int IS NULL THEN NULL ELSE (
|
||
SELECT hierarchy_level FROM falukant_type.church_office_type WHERE id = $2::int
|
||
) END AS prereq_hl
|
||
),
|
||
char_h AS (
|
||
SELECT
|
||
c.id AS character_id,
|
||
c.title_of_nobility,
|
||
t.level AS title_level,
|
||
EXISTS(
|
||
SELECT 1
|
||
FROM falukant_data.church_office co2
|
||
WHERE co2.character_id = c.id
|
||
) AS has_office,
|
||
COALESCE(c.highest_church_hierarchy_ever, 0)::int AS highest_ever,
|
||
COALESCE((
|
||
SELECT MAX(cot2.hierarchy_level)
|
||
FROM falukant_data.church_office co2
|
||
JOIN falukant_type.church_office_type cot2 ON co2.office_type_id = cot2.id
|
||
WHERE co2.character_id = c.id
|
||
), 0) AS current_max_hl
|
||
FROM falukant_data.character c
|
||
LEFT JOIN falukant_type.title t ON c.title_of_nobility = t.id
|
||
WHERE c.id = $1
|
||
)
|
||
SELECT
|
||
ch.character_id,
|
||
ch.title_level,
|
||
ch.has_office,
|
||
CASE
|
||
WHEN pr.prereq_type_id IS NULL THEN TRUE
|
||
ELSE (
|
||
EXISTS(
|
||
SELECT 1
|
||
FROM falukant_data.church_office co
|
||
WHERE co.character_id = $1
|
||
AND co.office_type_id = pr.prereq_type_id
|
||
)
|
||
OR (
|
||
pr.prereq_hl IS NOT NULL
|
||
AND GREATEST(ch.highest_ever, ch.current_max_hl) >= pr.prereq_hl
|
||
)
|
||
)
|
||
END AS has_prerequisite,
|
||
CASE
|
||
WHEN $3::int IS NULL THEN TRUE
|
||
ELSE COALESCE(ch.title_level, 0) >= $3::int
|
||
END AS meets_title_requirement
|
||
FROM char_h ch
|
||
CROSS JOIN prereq pr;
|
||
"#;
|
||
|
||
pub const QUERY_APPROVE_CHURCH_APPLICATION: &str = r#"
|
||
WITH updated_application AS (
|
||
UPDATE falukant_data.church_application
|
||
SET status = 'approved',
|
||
decision_date = NOW(),
|
||
updated_at = NOW()
|
||
WHERE id = $1
|
||
AND status = 'pending'
|
||
RETURNING
|
||
office_type_id,
|
||
character_id,
|
||
region_id,
|
||
supervisor_id
|
||
),
|
||
inserted_office AS (
|
||
INSERT INTO falukant_data.church_office
|
||
(office_type_id, character_id, region_id, supervisor_id, created_at, updated_at)
|
||
SELECT
|
||
office_type_id,
|
||
character_id,
|
||
region_id,
|
||
supervisor_id,
|
||
NOW(),
|
||
NOW()
|
||
FROM updated_application
|
||
WHERE NOT EXISTS(
|
||
SELECT 1
|
||
FROM falukant_data.church_office co
|
||
WHERE co.office_type_id = updated_application.office_type_id
|
||
AND co.region_id = updated_application.region_id
|
||
AND co.character_id = updated_application.character_id
|
||
)
|
||
RETURNING id, office_type_id, character_id, region_id
|
||
),
|
||
upd_highest AS (
|
||
UPDATE falukant_data.character c
|
||
SET highest_church_hierarchy_ever = GREATEST(
|
||
COALESCE(c.highest_church_hierarchy_ever, 0),
|
||
io.hl
|
||
)::smallint
|
||
FROM (
|
||
SELECT io2.character_id, cot.hierarchy_level AS hl
|
||
FROM inserted_office io2
|
||
JOIN falukant_type.church_office_type cot ON cot.id = io2.office_type_id
|
||
) io
|
||
WHERE c.id = io.character_id
|
||
RETURNING c.id
|
||
),
|
||
remove_lower_ranked AS (
|
||
DELETE FROM falukant_data.church_office co
|
||
WHERE co.id IN (
|
||
SELECT co3.id
|
||
FROM falukant_data.church_office co3
|
||
JOIN falukant_type.church_office_type cot ON co3.office_type_id = cot.id
|
||
WHERE co3.character_id IN (SELECT character_id FROM inserted_office)
|
||
AND EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.church_office co2
|
||
JOIN falukant_type.church_office_type cot2 ON co2.office_type_id = cot2.id
|
||
WHERE co2.character_id = co3.character_id
|
||
AND cot2.hierarchy_level > cot.hierarchy_level
|
||
)
|
||
)
|
||
)
|
||
SELECT
|
||
id AS office_id,
|
||
office_type_id,
|
||
character_id,
|
||
region_id
|
||
FROM inserted_office;
|
||
"#;
|
||
|
||
pub const QUERY_REJECT_CHURCH_APPLICATION: &str = r#"
|
||
UPDATE falukant_data.church_application
|
||
SET status = 'rejected',
|
||
decision_date = NOW(),
|
||
updated_at = NOW()
|
||
WHERE id = $1
|
||
AND status = 'pending'
|
||
RETURNING id;
|
||
"#;
|
||
|
||
/// Nur NPC-Vorgesetzte: Spieler-Entscheidungen nicht per Timeout überschreiben.
|
||
pub const QUERY_GET_OLD_PENDING_CHURCH_APPLICATIONS: &str = r#"
|
||
SELECT
|
||
ca.id AS application_id,
|
||
ca.office_type_id,
|
||
ca.character_id,
|
||
ca.region_id,
|
||
ca.supervisor_id
|
||
FROM falukant_data.church_application ca
|
||
JOIN falukant_data.character sup ON sup.id = ca.supervisor_id
|
||
WHERE ca.status = 'pending'
|
||
AND ca.created_at <= NOW() - INTERVAL '36 hours'
|
||
AND sup.user_id IS NULL
|
||
ORDER BY ca.created_at ASC;
|
||
"#;
|
||
|
||
pub const QUERY_AUTO_APPROVE_CHURCH_APPLICATION: &str = r#"
|
||
WITH updated_application AS (
|
||
UPDATE falukant_data.church_application
|
||
SET status = 'approved',
|
||
decision_date = NOW(),
|
||
updated_at = NOW()
|
||
WHERE id = $1
|
||
AND status = 'pending'
|
||
AND created_at <= NOW() - INTERVAL '36 hours'
|
||
RETURNING
|
||
office_type_id,
|
||
character_id,
|
||
region_id,
|
||
supervisor_id
|
||
),
|
||
inserted_office AS (
|
||
INSERT INTO falukant_data.church_office
|
||
(office_type_id, character_id, region_id, supervisor_id, created_at, updated_at)
|
||
SELECT
|
||
office_type_id,
|
||
character_id,
|
||
region_id,
|
||
supervisor_id,
|
||
NOW(),
|
||
NOW()
|
||
FROM updated_application
|
||
WHERE NOT EXISTS(
|
||
SELECT 1
|
||
FROM falukant_data.church_office co
|
||
WHERE co.office_type_id = updated_application.office_type_id
|
||
AND co.region_id = updated_application.region_id
|
||
AND co.character_id = updated_application.character_id
|
||
)
|
||
RETURNING id, office_type_id, character_id, region_id
|
||
),
|
||
upd_highest AS (
|
||
UPDATE falukant_data.character c
|
||
SET highest_church_hierarchy_ever = GREATEST(
|
||
COALESCE(c.highest_church_hierarchy_ever, 0),
|
||
io.hl
|
||
)::smallint
|
||
FROM (
|
||
SELECT io2.character_id, cot.hierarchy_level AS hl
|
||
FROM inserted_office io2
|
||
JOIN falukant_type.church_office_type cot ON cot.id = io2.office_type_id
|
||
) io
|
||
WHERE c.id = io.character_id
|
||
RETURNING c.id
|
||
),
|
||
remove_lower_ranked AS (
|
||
DELETE FROM falukant_data.church_office co
|
||
WHERE co.id IN (
|
||
SELECT co3.id
|
||
FROM falukant_data.church_office co3
|
||
JOIN falukant_type.church_office_type cot ON co3.office_type_id = cot.id
|
||
WHERE co3.character_id IN (SELECT character_id FROM inserted_office)
|
||
AND EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.church_office co2
|
||
JOIN falukant_type.church_office_type cot2 ON co2.office_type_id = cot2.id
|
||
WHERE co2.character_id = co3.character_id
|
||
AND cot2.hierarchy_level > cot.hierarchy_level
|
||
)
|
||
)
|
||
)
|
||
SELECT
|
||
id AS office_id,
|
||
office_type_id,
|
||
character_id,
|
||
region_id
|
||
FROM inserted_office;
|
||
"#;
|
||
|
||
pub const QUERY_CREATE_CHURCH_APPLICATION_JOB: &str = r#"
|
||
INSERT INTO falukant_data.church_application
|
||
(office_type_id, character_id, region_id, supervisor_id, status, created_at, updated_at)
|
||
SELECT
|
||
$1::int AS office_type_id,
|
||
$2::int AS character_id,
|
||
$3::int AS region_id,
|
||
$4::int AS supervisor_id,
|
||
'pending' AS status,
|
||
NOW() AS created_at,
|
||
NOW() AS updated_at
|
||
WHERE NOT EXISTS(
|
||
SELECT 1
|
||
FROM falukant_data.church_application ca
|
||
WHERE ca.office_type_id = $1::int
|
||
AND ca.character_id = $2::int
|
||
AND ca.region_id = $3::int
|
||
AND ca.status = 'pending'
|
||
)
|
||
RETURNING id;
|
||
"#;
|
||
|
||
/// Nur NPCs: Spielerbewerbungen laufen über die UI.
|
||
pub const QUERY_GET_CHARACTERS_FOR_CHURCH_OFFICE: &str = r#"
|
||
SELECT DISTINCT
|
||
c.id AS character_id,
|
||
c.user_id,
|
||
c.region_id,
|
||
c.title_of_nobility,
|
||
t.level AS title_level
|
||
FROM falukant_data.character c
|
||
LEFT JOIN falukant_type.title t ON c.title_of_nobility = t.id
|
||
WHERE c.region_id = $1
|
||
AND c.health > 0
|
||
AND c.user_id IS NULL
|
||
AND NOT EXISTS(
|
||
SELECT 1
|
||
FROM falukant_data.church_office co
|
||
WHERE co.character_id = c.id
|
||
)
|
||
ORDER BY RANDOM()
|
||
LIMIT $2;
|
||
"#;
|
||
|
||
pub const QUERY_COUNT_PENDING_CHURCH_APPS_BY_OFFICE_REGION: &str = r#"
|
||
SELECT COUNT(*)::int AS cnt
|
||
FROM falukant_data.church_application ca
|
||
WHERE ca.office_type_id = $1::int
|
||
AND ca.region_id = $2::int
|
||
AND ca.status = 'pending';
|
||
"#;
|
||
|
||
pub const QUERY_GET_CHURCH_OFFICE_OCCUPIED_COUNT: &str = r#"
|
||
SELECT COUNT(*)::int AS cnt
|
||
FROM falukant_data.church_office co
|
||
WHERE co.office_type_id = $1::int
|
||
AND co.region_id = $2::int;
|
||
"#;
|
||
|
||
pub const QUERY_IS_CHARACTER_NPC: &str = r#"
|
||
SELECT (c.user_id IS NULL) AS is_npc
|
||
FROM falukant_data.character c
|
||
WHERE c.id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_GET_PENDING_CHURCH_APPLICATIONS_FOR_SCORING: &str = r#"
|
||
SELECT
|
||
ca.id AS application_id,
|
||
ca.office_type_id,
|
||
ca.character_id AS applicant_character_id,
|
||
ca.region_id,
|
||
ca.created_at,
|
||
cot.hierarchy_level AS office_hierarchy_level,
|
||
cot.seats_per_region,
|
||
COALESCE(sc.reputation, 50)::float8 AS supervisor_reputation,
|
||
COALESCE(ac.reputation, 50)::float8 AS applicant_reputation,
|
||
COALESCE(ac.highest_church_hierarchy_ever, 0)::int AS applicant_highest_ever,
|
||
COALESCE(t.level, 0)::int AS applicant_title_level,
|
||
COALESCE((
|
||
SELECT MAX(cot2.hierarchy_level)
|
||
FROM falukant_data.church_office co2
|
||
JOIN falukant_type.church_office_type cot2 ON co2.office_type_id = cot2.id
|
||
WHERE co2.character_id = ac.id
|
||
), 0)::int AS applicant_current_max_hierarchy,
|
||
(CURRENT_DATE - ac.birthdate::date)::int AS applicant_age_days
|
||
FROM falukant_data.church_application ca
|
||
JOIN falukant_data.character ac ON ac.id = ca.character_id
|
||
JOIN falukant_data.character sc ON sc.id = ca.supervisor_id
|
||
JOIN falukant_type.church_office_type cot ON cot.id = ca.office_type_id
|
||
LEFT JOIN falukant_type.title t ON t.id = ac.title_of_nobility
|
||
WHERE ca.status = 'pending'
|
||
AND ca.supervisor_id = $1::int
|
||
ORDER BY ca.created_at ASC;
|
||
"#;
|
||
|
||
pub const QUERY_INTERIM_APPOINT_CHURCH_OFFICE: &str = r#"
|
||
INSERT INTO falukant_data.church_office
|
||
(office_type_id, character_id, region_id, supervisor_id, created_at, updated_at)
|
||
SELECT $1::int, $2::int, $3::int, NULL, NOW(), NOW()
|
||
WHERE (
|
||
SELECT COUNT(*)::int
|
||
FROM falukant_data.church_office co
|
||
WHERE co.office_type_id = $1::int
|
||
AND co.region_id = $3::int
|
||
) < $4::int
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM falukant_data.church_office co
|
||
WHERE co.character_id = $2::int
|
||
AND co.office_type_id = $1::int
|
||
AND co.region_id = $3::int
|
||
)
|
||
RETURNING id, office_type_id, character_id, region_id;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_CHARACTER_HIGHEST_CHURCH_FROM_OFFICE_TYPE: &str = r#"
|
||
UPDATE falukant_data.character c
|
||
SET highest_church_hierarchy_ever = GREATEST(
|
||
COALESCE(c.highest_church_hierarchy_ever, 0),
|
||
(SELECT cot.hierarchy_level FROM falukant_type.church_office_type cot WHERE cot.id = $2::int)
|
||
)::smallint
|
||
WHERE c.id = $1::int
|
||
RETURNING c.id;
|
||
"#;
|
||
|
||
pub const QUERY_FIND_INTERIM_CHURCH_NPC_CANDIDATE: &str = r#"
|
||
SELECT c.id AS character_id
|
||
FROM falukant_data.character c
|
||
WHERE c.region_id = $1::int
|
||
AND c.user_id IS NULL
|
||
AND c.health > 0
|
||
AND NOT EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.church_office co
|
||
JOIN falukant_type.church_office_type cot ON cot.id = co.office_type_id
|
||
WHERE co.character_id = c.id
|
||
AND cot.hierarchy_level >= (
|
||
SELECT hierarchy_level FROM falukant_type.church_office_type WHERE id = $2::int
|
||
)
|
||
)
|
||
ORDER BY COALESCE(c.reputation, 50) DESC,
|
||
COALESCE(c.highest_church_hierarchy_ever, 0) DESC
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_REMOVE_LOWER_CHURCH_OFFICES_FOR_CHARACTER: &str = r#"
|
||
DELETE FROM falukant_data.church_office co
|
||
WHERE co.character_id = $1::int
|
||
AND co.id IN (
|
||
SELECT co3.id
|
||
FROM falukant_data.church_office co3
|
||
JOIN falukant_type.church_office_type cot ON co3.office_type_id = cot.id
|
||
WHERE co3.character_id = $1::int
|
||
AND EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.church_office co2
|
||
JOIN falukant_type.church_office_type cot2 ON co2.office_type_id = cot2.id
|
||
WHERE co2.character_id = co3.character_id
|
||
AND cot2.hierarchy_level > cot.hierarchy_level
|
||
)
|
||
);
|
||
"#;
|
||
|
||
// --- Falukant: Dienerschaft (siehe migrations/004_falukant_servants_daemon.sql) ---
|
||
|
||
pub const QUERY_SERVANTS_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'user_house'
|
||
AND column_name = 'servants_last_daily_at'
|
||
) AS ready;
|
||
"#;
|
||
|
||
pub const QUERY_GET_SERVANT_DAILY_ROWS: &str = r#"
|
||
SELECT DISTINCT ON (uh.id)
|
||
uh.id AS user_house_id,
|
||
fu.id AS falukant_user_id,
|
||
c.id AS character_id,
|
||
COALESCE(c.reputation, 50)::float8 AS reputation,
|
||
COALESCE(t.level, 0)::int AS title_level,
|
||
COALESCE(ht.position, 0)::int AS house_position,
|
||
COALESCE(ht.cost, 0)::bigint AS house_cost,
|
||
uh.servant_count,
|
||
uh.servant_quality,
|
||
COALESCE(NULLIF(TRIM(uh.servant_pay_level), ''), 'normal') AS servant_pay_level,
|
||
uh.household_order
|
||
FROM falukant_data.user_house uh
|
||
JOIN falukant_data.falukant_user fu ON fu.id = uh.user_id
|
||
JOIN falukant_data.character c ON c.user_id = fu.id AND c.health > 0
|
||
LEFT JOIN falukant_type.title t ON t.id = c.title_of_nobility
|
||
LEFT JOIN falukant_type.house ht ON ht.id = uh.house_type_id
|
||
WHERE (uh.servants_last_daily_at IS NULL OR (uh.servants_last_daily_at::date < CURRENT_DATE))
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM falukant_type.house h
|
||
WHERE h.id = uh.house_type_id AND h.label_tr = 'under_bridge'
|
||
)
|
||
ORDER BY uh.id, c.id;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_USER_HOUSE_SERVANT_DAILY: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET household_order = $1::smallint,
|
||
servant_quality = $2::smallint,
|
||
servant_discretion_modifier = $3::smallint,
|
||
servants_last_daily_at = NOW()
|
||
WHERE id = $4::int;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_MARRIAGE_SATISFACTION_ADD_FOR_CHARACTER: &str = r#"
|
||
UPDATE falukant_data.relationship r
|
||
SET marriage_satisfaction = GREATEST(0, LEAST(100, marriage_satisfaction + $1::int))
|
||
FROM falukant_type.relationship rt
|
||
WHERE rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
AND (r.character1_id = $2::int OR r.character2_id = $2::int);
|
||
"#;
|
||
|
||
pub const QUERY_GET_SERVANT_MONTHLY_ROWS: &str = r#"
|
||
SELECT DISTINCT ON (uh.id)
|
||
uh.id AS user_house_id,
|
||
fu.id AS falukant_user_id,
|
||
c.id AS character_id,
|
||
COALESCE(c.reputation, 50)::float8 AS reputation,
|
||
COALESCE(t.level, 0)::int AS title_level,
|
||
COALESCE(ht.position, 0)::int AS house_position,
|
||
COALESCE(ht.cost, 0)::bigint AS house_cost,
|
||
uh.servant_count,
|
||
uh.servant_quality,
|
||
COALESCE(NULLIF(TRIM(uh.servant_pay_level), ''), 'normal') AS servant_pay_level,
|
||
uh.household_order,
|
||
COALESCE(fu.money, 0)::float8 AS user_money,
|
||
uh.servants_underfunded
|
||
FROM falukant_data.user_house uh
|
||
JOIN falukant_data.falukant_user fu ON fu.id = uh.user_id
|
||
JOIN falukant_data.character c ON c.user_id = fu.id AND c.health > 0
|
||
LEFT JOIN falukant_type.title t ON t.id = c.title_of_nobility
|
||
LEFT JOIN falukant_type.house ht ON ht.id = uh.house_type_id
|
||
-- Monatstick (Spielzeit): alle ~2 h, nicht Kalendermonat (siehe docs/FALUKANT_DAEMON_AENDERUNGSNOTIZ_ZEITMASSSTAB.md)
|
||
WHERE (uh.servants_last_monthly_at IS NULL
|
||
OR uh.servants_last_monthly_at < NOW() - INTERVAL '2 hours')
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM falukant_type.house h
|
||
WHERE h.id = uh.house_type_id AND h.label_tr = 'under_bridge'
|
||
)
|
||
ORDER BY uh.id, c.id;
|
||
"#;
|
||
|
||
pub const QUERY_COUNT_ACTIVE_LOVERS_FOR_CHARACTER: &str = r#"
|
||
SELECT COUNT(*)::int AS cnt
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id AND rs.active = true
|
||
WHERE r.character1_id = $1::int OR r.character2_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_USER_HOUSE_SERVANT_MONTHLY_META: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET servants_underfunded = $1::boolean,
|
||
servants_last_monthly_at = NOW()
|
||
WHERE id = $2::int;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_USER_HOUSE_SERVANT_UNDERFUNDED_PENALTY: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET servant_quality = GREATEST(0, servant_quality - 4),
|
||
household_order = GREATEST(0, household_order - 6),
|
||
servant_discretion_modifier = GREATEST(-100, LEAST(100,
|
||
COALESCE(servant_discretion_modifier, 0) + $1::int))
|
||
WHERE id = $2::int;
|
||
"#;
|
||
|
||
// --- Falukant: Familie / Liebhaber / Ehezufriedenheit (siehe migrations/001_falukant_family_lovers.sql) ---
|
||
|
||
pub const QUERY_FAMILY_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'relationship_state'
|
||
AND column_name = 'last_daily_processed_at'
|
||
) AS ready;
|
||
"#;
|
||
|
||
pub const QUERY_GET_ACTIVE_LOVER_ROWS_FOR_DAILY: &str = r#"
|
||
SELECT
|
||
r.id AS rel_id,
|
||
r.character1_id AS c1,
|
||
r.character2_id AS c2,
|
||
rs.lover_role,
|
||
rs.affection,
|
||
rs.visibility,
|
||
rs.discretion,
|
||
rs.maintenance_level,
|
||
rs.status_fit,
|
||
rs.monthly_base_cost,
|
||
rs.scandal_extra_daily_pct,
|
||
rs.months_underfunded,
|
||
COALESCE(rs.acknowledged, false) AS acknowledged,
|
||
c1.gender AS g1,
|
||
c2.gender AS g2,
|
||
COALESCE(t1.tr, '') AS title1_tr,
|
||
COALESCE(t2.tr, '') AS title2_tr,
|
||
COALESCE(c1.reputation, 50)::float8 AS rep1,
|
||
COALESCE(c2.reputation, 50)::float8 AS rep2,
|
||
fu1.id AS user1_id,
|
||
fu2.id AS user2_id,
|
||
LEAST(
|
||
((CURRENT_DATE - c1.birthdate::date) / 365),
|
||
((CURRENT_DATE - c2.birthdate::date) / 365)
|
||
)::int AS min_age_years,
|
||
COALESCE((
|
||
SELECT MAX(uh.servant_discretion_modifier)::int
|
||
FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = fu1.id
|
||
), 0) AS servant_disc_u1,
|
||
COALESCE((
|
||
SELECT MAX(uh.servant_discretion_modifier)::int
|
||
FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = fu2.id
|
||
), 0) AS servant_disc_u2
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt
|
||
ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE rs.active = true
|
||
AND (
|
||
rs.last_daily_processed_at IS NULL
|
||
OR (rs.last_daily_processed_at::date < CURRENT_DATE)
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_GET_ACTIVE_LOVER_ROWS_FOR_MONTHLY: &str = r#"
|
||
SELECT
|
||
r.id AS rel_id,
|
||
r.character1_id AS c1,
|
||
r.character2_id AS c2,
|
||
rs.lover_role,
|
||
rs.affection,
|
||
rs.visibility,
|
||
rs.discretion,
|
||
rs.maintenance_level,
|
||
rs.status_fit,
|
||
rs.monthly_base_cost,
|
||
rs.scandal_extra_daily_pct,
|
||
rs.months_underfunded,
|
||
c1.gender AS g1,
|
||
c2.gender AS g2,
|
||
COALESCE(t1.tr, '') AS title1_tr,
|
||
COALESCE(t2.tr, '') AS title2_tr,
|
||
COALESCE(c1.reputation, 50)::float8 AS rep1,
|
||
COALESCE(c2.reputation, 50)::float8 AS rep2,
|
||
fu1.id AS user1_id,
|
||
fu2.id AS user2_id,
|
||
LEAST(
|
||
((CURRENT_DATE - c1.birthdate::date) / 365),
|
||
((CURRENT_DATE - c2.birthdate::date) / 365)
|
||
)::int AS min_age_years
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt
|
||
ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE rs.active = true
|
||
AND (
|
||
rs.last_monthly_processed_at IS NULL
|
||
OR rs.last_monthly_processed_at::date < CURRENT_DATE
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_LOVER_VISIBILITY_DISCRETION: &str = r#"
|
||
UPDATE falukant_data.relationship_state
|
||
SET visibility = $1::smallint,
|
||
discretion = $2::smallint
|
||
WHERE relationship_id = $3::int;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_LOVER_UNDERPAY_STATE: &str = r#"
|
||
UPDATE falukant_data.relationship_state
|
||
SET affection = $1::smallint,
|
||
discretion = $2::smallint,
|
||
visibility = $3::smallint,
|
||
months_underfunded = $4::smallint,
|
||
scandal_extra_daily_pct = $5::smallint
|
||
WHERE relationship_id = $6::int;
|
||
"#;
|
||
|
||
pub const QUERY_GET_MARRIAGE_ROWS: &str = r#"
|
||
SELECT
|
||
r.id AS marriage_id,
|
||
r.character1_id AS m1,
|
||
r.character2_id AS m2,
|
||
r.marriage_satisfaction,
|
||
COALESCE(r.marriage_public_stability, 55)::int AS marriage_public_stability,
|
||
r.marriage_drift_high,
|
||
r.marriage_drift_low,
|
||
COALESCE(t1.tr, '') AS title1_tr,
|
||
COALESCE(t2.tr, '') AS title2_tr,
|
||
fu1.id AS user1_id,
|
||
fu2.id AS user2_id,
|
||
COALESCE(r.marriage_gift_buff_days_remaining, 0)::int AS marriage_gift_buff_days_remaining,
|
||
COALESCE(r.marriage_pending_feast_bonus, 0)::int AS marriage_pending_feast_bonus,
|
||
COALESCE(r.marriage_house_supply, 50)::int AS marriage_house_supply,
|
||
COALESCE(r.marriage_no_lover_bonus_counter, 0)::int AS marriage_no_lover_bonus_counter,
|
||
COALESCE((
|
||
SELECT uh.household_order FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = c1.user_id ORDER BY uh.id LIMIT 1
|
||
), 55)::int AS household_order_1,
|
||
COALESCE((
|
||
SELECT uh.household_order FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = c2.user_id ORDER BY uh.id LIMIT 1
|
||
), 55)::int AS household_order_2,
|
||
COALESCE((
|
||
SELECT uh.servant_quality FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = c1.user_id ORDER BY uh.id LIMIT 1
|
||
), 50)::int AS servant_quality_1,
|
||
COALESCE((
|
||
SELECT uh.servant_quality FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = c2.user_id ORDER BY uh.id LIMIT 1
|
||
), 50)::int AS servant_quality_2
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id;
|
||
"#;
|
||
|
||
#[allow(dead_code)] // Einfaches Update ohne Buff-Spalten; Daemon nutzt QUERY_UPDATE_MARRIAGE_STATE_AND_BUFFS.
|
||
pub const QUERY_UPDATE_MARRIAGE_STATE: &str = r#"
|
||
UPDATE falukant_data.relationship
|
||
SET marriage_satisfaction = $1::smallint,
|
||
marriage_drift_high = $2::smallint,
|
||
marriage_drift_low = $3::smallint
|
||
WHERE id = $4::int;
|
||
"#;
|
||
|
||
/// Inkl. Geschenk-/Fest-/Haus-Zähler (Migration `003_falukant_family_marriage_buffs.sql`).
|
||
/// `marriage_public_stability`: Migration `005_falukant_marriage_housepeace.sql`.
|
||
pub const QUERY_UPDATE_MARRIAGE_STATE_AND_BUFFS: &str = r#"
|
||
UPDATE falukant_data.relationship
|
||
SET marriage_satisfaction = $1::smallint,
|
||
marriage_drift_high = $2::smallint,
|
||
marriage_drift_low = $3::smallint,
|
||
marriage_gift_buff_days_remaining = $4::smallint,
|
||
marriage_pending_feast_bonus = $5::smallint,
|
||
marriage_no_lover_bonus_counter = $6::smallint,
|
||
marriage_public_stability = $7::smallint
|
||
WHERE id = $8::int;
|
||
"#;
|
||
|
||
/// Persistiert berechneten Hausfrieden (0..100), Migration `005_falukant_marriage_housepeace.sql`.
|
||
pub const QUERY_UPDATE_USER_HOUSE_TENSION_BY_USER: &str = r#"
|
||
UPDATE falukant_data.user_house
|
||
SET household_tension_score = $1::smallint
|
||
WHERE user_id = $2::int;
|
||
"#;
|
||
|
||
/// Kinder aus Liebschaften, die einen Charakter dieses Falukant-Users betreffen.
|
||
pub const QUERY_COUNT_LOVER_CHILDREN_FOR_USER: &str = r#"
|
||
SELECT COUNT(*)::int AS cnt
|
||
FROM falukant_data.child_relation cr
|
||
WHERE cr.birth_context = 'lover'
|
||
AND (
|
||
EXISTS (
|
||
SELECT 1 FROM falukant_data.character c
|
||
WHERE c.id = cr.father_character_id AND c.user_id = $1::int
|
||
)
|
||
OR EXISTS (
|
||
SELECT 1 FROM falukant_data.character c
|
||
WHERE c.id = cr.mother_character_id AND c.user_id = $1::int
|
||
)
|
||
);
|
||
"#;
|
||
|
||
/// Haushalt für Spannungsberechnung (ein Eintrag pro Zeile; typisch eine Zeile pro User).
|
||
pub const QUERY_GET_USER_HOUSE_ROW_BY_USER: &str = r#"
|
||
SELECT uh.id AS user_house_id,
|
||
uh.user_id AS falukant_user_id,
|
||
COALESCE(uh.household_order, 55)::int AS household_order,
|
||
COALESCE(uh.servant_count, 0)::int AS servant_count,
|
||
COALESCE(uh.servant_quality, 50)::int AS servant_quality,
|
||
COALESCE(NULLIF(TRIM(uh.servant_pay_level), ''), 'normal') AS servant_pay_level,
|
||
COALESCE(uh.household_tension_score, 0)::int AS prev_tension_score
|
||
FROM falukant_data.user_house uh
|
||
WHERE uh.user_id = $1::int
|
||
ORDER BY uh.id
|
||
LIMIT 1;
|
||
"#;
|
||
|
||
pub const QUERY_MARRIAGE_SUBTRACT_SATISFACTION: &str = r#"
|
||
UPDATE falukant_data.relationship r
|
||
SET marriage_satisfaction = GREATEST(0, r.marriage_satisfaction - $2::int)
|
||
FROM falukant_type.relationship rt
|
||
WHERE rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
AND r.id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_RESET_LOVER_UNDERPAY_COUNTERS: &str = r#"
|
||
UPDATE falukant_data.relationship_state
|
||
SET months_underfunded = 0
|
||
WHERE relationship_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_MARK_LOVER_DAILY_DONE: &str = r#"
|
||
UPDATE falukant_data.relationship_state
|
||
SET last_daily_processed_at = NOW()
|
||
WHERE relationship_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_MARK_LOVER_MONTHLY_DONE: &str = r#"
|
||
UPDATE falukant_data.relationship_state
|
||
SET last_monthly_processed_at = NOW()
|
||
WHERE relationship_id = $1::int;
|
||
"#;
|
||
|
||
/// Liebschaft: fällige Teilzahlung (alle 2 h), Migration `006_falukant_lover_installments.sql`.
|
||
pub const QUERY_GET_ACTIVE_LOVER_ROWS_FOR_INSTALLMENT: &str = r#"
|
||
SELECT
|
||
r.id AS rel_id,
|
||
r.character1_id AS c1,
|
||
r.character2_id AS c2,
|
||
rs.lover_role,
|
||
rs.affection,
|
||
rs.visibility,
|
||
rs.discretion,
|
||
rs.maintenance_level,
|
||
rs.status_fit,
|
||
rs.monthly_base_cost,
|
||
rs.scandal_extra_daily_pct,
|
||
rs.months_underfunded,
|
||
c1.gender AS g1,
|
||
c2.gender AS g2,
|
||
COALESCE(t1.tr, '') AS title1_tr,
|
||
COALESCE(t2.tr, '') AS title2_tr,
|
||
COALESCE(c1.reputation, 50)::float8 AS rep1,
|
||
COALESCE(c2.reputation, 50)::float8 AS rep2,
|
||
fu1.id AS user1_id,
|
||
fu2.id AS user2_id,
|
||
LEAST(
|
||
((CURRENT_DATE - c1.birthdate::date) / 365),
|
||
((CURRENT_DATE - c2.birthdate::date) / 365)
|
||
)::int AS min_age_years
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt
|
||
ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
LEFT JOIN falukant_type.title t1 ON t1.id = c1.title_of_nobility
|
||
LEFT JOIN falukant_type.title t2 ON t2.id = c2.title_of_nobility
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE rs.active = true
|
||
AND (
|
||
rs.lover_last_installment_at IS NULL
|
||
OR rs.lover_last_installment_at < NOW() - INTERVAL '2 hours'
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_MARK_LOVER_INSTALLMENT_AT: &str = r#"
|
||
UPDATE falukant_data.relationship_state
|
||
SET lover_last_installment_at = NOW()
|
||
WHERE relationship_id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_LOVER_INSTALLMENT_SCHEMA_READY: &str = r#"
|
||
SELECT EXISTS (
|
||
SELECT 1
|
||
FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'relationship_state'
|
||
AND column_name = 'lover_last_installment_at'
|
||
) AS ready;
|
||
"#;
|
||
|
||
pub const QUERY_UPDATE_CHARACTER_REPUTATION: &str = r#"
|
||
UPDATE falukant_data.character
|
||
SET reputation = $1::numeric,
|
||
updated_at = NOW()
|
||
WHERE id = $2::int;
|
||
"#;
|
||
|
||
pub const QUERY_GET_LOVER_PREGNANCY_CANDIDATES: &str = r#"
|
||
SELECT
|
||
CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END AS father_cid,
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END AS mother_cid,
|
||
CASE WHEN c1.gender = 'male' THEN c1.title_of_nobility ELSE c2.title_of_nobility END AS title_of_nobility,
|
||
CASE WHEN c1.gender = 'male' THEN c1.last_name ELSE c2.last_name END AS last_name,
|
||
CASE WHEN c1.gender = 'male' THEN c1.region_id ELSE c2.region_id END AS region_id,
|
||
CASE WHEN c1.gender = 'male' THEN fu1.id ELSE fu2.id END AS father_uid,
|
||
CASE WHEN c1.gender = 'female' THEN fu1.id ELSE fu2.id END AS mother_uid,
|
||
(CURRENT_DATE - c_female.birthdate::date)::int AS mother_age_days
|
||
FROM falukant_data.relationship r
|
||
JOIN falukant_type.relationship rt ON rt.id = r.relationship_type_id AND rt.tr = 'lover'
|
||
JOIN falukant_data.relationship_state rs ON rs.relationship_id = r.id AND rs.active = true
|
||
JOIN falukant_data.character c1 ON c1.id = r.character1_id
|
||
JOIN falukant_data.character c2 ON c2.id = r.character2_id
|
||
JOIN falukant_data.character c_female ON c_female.id = (
|
||
CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END
|
||
)
|
||
LEFT JOIN falukant_data.falukant_user fu1 ON fu1.id = c1.user_id
|
||
LEFT JOIN falukant_data.falukant_user fu2 ON fu2.id = c2.user_id
|
||
WHERE (c1.gender = 'female' AND c2.gender = 'male')
|
||
OR (c1.gender = 'male' AND c2.gender = 'female')
|
||
AND rs.affection >= 45
|
||
AND rs.maintenance_level >= 30
|
||
-- `last_monthly_processed_at` wird im Familien-Tageslauf mitgeführt (1 Spieljahr = 1 Kalendertag).
|
||
AND rs.last_monthly_processed_at IS NOT NULL
|
||
AND rs.last_monthly_processed_at >= NOW() - INTERVAL '50 days'
|
||
AND NOT EXISTS (
|
||
SELECT 1
|
||
FROM falukant_data.child_relation cr
|
||
WHERE cr.father_character_id = (CASE WHEN c1.gender = 'male' THEN c1.id ELSE c2.id END)
|
||
AND cr.mother_character_id = (CASE WHEN c1.gender = 'female' THEN c1.id ELSE c2.id END)
|
||
AND cr.created_at::date >= CURRENT_DATE
|
||
)
|
||
AND (CURRENT_DATE - c_female.birthdate::date) >= 4380
|
||
AND (CURRENT_DATE - c_female.birthdate::date) < 18993
|
||
AND random() * 100.0 < (
|
||
LEAST(12.0, GREATEST(0.0,
|
||
CASE rs.lover_role
|
||
WHEN 'secret_affair' THEN 2.0
|
||
WHEN 'lover' THEN 4.0
|
||
WHEN 'mistress_or_favorite' THEN 6.0
|
||
ELSE 0.0
|
||
END
|
||
+ CASE WHEN rs.affection >= 75 THEN 2.0 ELSE 0.0 END
|
||
+ CASE WHEN rs.visibility >= 70 AND rs.affection < 50 THEN -2.0 ELSE 0.0 END
|
||
+ CASE
|
||
WHEN (CURRENT_DATE - c_female.birthdate::date) > 14600
|
||
THEN -3.0
|
||
ELSE 0.0
|
||
END
|
||
))
|
||
);
|
||
"#;
|
||
|
||
pub const QUERY_LOVER_BIRTH_PENALTY_MARRIAGE: &str = r#"
|
||
UPDATE falukant_data.relationship r
|
||
SET marriage_satisfaction = GREATEST(0, r.marriage_satisfaction - 8)
|
||
FROM falukant_type.relationship rt
|
||
WHERE rt.id = r.relationship_type_id
|
||
AND rt.tr IN ('married', 'engaged', 'wooing')
|
||
AND (r.character1_id = $1::int OR r.character2_id = $1::int);
|
||
"#;
|
||
|
||
pub const QUERY_LOVER_BIRTH_PENALTY_REPUTATION: &str = r#"
|
||
UPDATE falukant_data.character
|
||
SET reputation = GREATEST(0::numeric, COALESCE(reputation, 50::numeric) - 4::numeric),
|
||
updated_at = NOW()
|
||
WHERE id = $1::int;
|
||
"#;
|
||
|
||
pub const QUERY_INSERT_CHILD_RELATION_LOVER: &str = r#"
|
||
INSERT INTO falukant_data.child_relation (
|
||
father_character_id,
|
||
mother_character_id,
|
||
child_character_id,
|
||
name_set,
|
||
legitimacy,
|
||
birth_context,
|
||
public_known,
|
||
created_at,
|
||
updated_at
|
||
)
|
||
VALUES (
|
||
$1::int,
|
||
$2::int,
|
||
$3::int,
|
||
FALSE,
|
||
'hidden_bastard',
|
||
'lover',
|
||
FALSE,
|
||
NOW(),
|
||
NOW()
|
||
);
|
||
"#;
|
||
|
||
// --- Produktionszertifikat (Daemon Daily, Spec: Produktionszertifikate) ---
|
||
|
||
/// Ein Spielercharakter pro Falukant-User (bei mehreren lebenden: **höchste** `character.id`,
|
||
/// typischerweise zuletzt aktiver Slot — konsistent mit UI, das oft den Hauptcharakter nutzt).
|
||
/// `completed_production_count`: Produktionen seit `certificate_productions_count_since` (Migration `014`); **NULL** = alle Log-Zeilen (Bestand vor erstem Aufstieg nach Migration).
|
||
pub const QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS: &str = r#"
|
||
SELECT DISTINCT ON (fu.id)
|
||
fu.id AS falukant_user_id,
|
||
COALESCE(fu.user_id, fu.id)::int AS app_user_id,
|
||
COALESCE(fu.certificate, 1)::int AS certificate,
|
||
COALESCE(fu.money, 0)::float8 AS money,
|
||
c.id AS character_id,
|
||
COALESCE(c.reputation, 50)::float8 AS reputation,
|
||
COALESCE(t.level, 0)::int AS title_level,
|
||
COALESCE((
|
||
SELECT AVG(k.knowledge)::float8
|
||
FROM falukant_data.knowledge k
|
||
WHERE k.character_id = c.id
|
||
), 0.0) AS avg_knowledge,
|
||
COALESCE((
|
||
SELECT COUNT(*)::bigint
|
||
FROM falukant_log.production pl
|
||
WHERE (pl.producer_id = fu.id OR pl.producer_id = c.id)
|
||
AND (
|
||
fu.certificate_productions_count_since IS NULL
|
||
OR COALESCE(
|
||
pl.production_timestamp,
|
||
pl.production_date::timestamp
|
||
) >= fu.certificate_productions_count_since
|
||
)
|
||
), 0) AS completed_production_count,
|
||
COALESCE((
|
||
SELECT MAX(cot.hierarchy_level)::int
|
||
FROM falukant_data.church_office co
|
||
JOIN falukant_type.church_office_type cot ON cot.id = co.office_type_id
|
||
WHERE co.character_id = c.id
|
||
), 0) AS max_church_hierarchy,
|
||
COALESCE((
|
||
SELECT STRING_AGG(DISTINCT pot.name, '|')
|
||
FROM falukant_data.political_office po
|
||
JOIN falukant_type.political_office_type pot ON pot.id = po.office_type_id
|
||
WHERE po.character_id = c.id
|
||
AND (po.created_at + (pot.term_length * INTERVAL '1 day')) > NOW()
|
||
), '') AS political_office_names,
|
||
COALESCE((
|
||
SELECT h.position::int
|
||
FROM falukant_data.user_house uh
|
||
JOIN falukant_type.house h ON h.id = uh.house_type_id
|
||
WHERE uh.user_id = fu.id
|
||
ORDER BY uh.id
|
||
LIMIT 1
|
||
), 0) AS house_position
|
||
FROM falukant_data.falukant_user fu
|
||
JOIN falukant_data.character c ON c.user_id = fu.id AND c.health > 0
|
||
LEFT JOIN falukant_type.title t ON t.id = c.title_of_nobility
|
||
ORDER BY fu.id, c.id DESC;
|
||
"#;
|
||
|
||
/// Setzt bei jeder Stufenänderung `certificate_productions_count_since` (Mindest-Produktionen / PP neu ab Aufstieg).
|
||
pub const QUERY_UPDATE_FALUKANT_USER_CERTIFICATE: &str = r#"
|
||
UPDATE falukant_data.falukant_user
|
||
SET certificate = $1::int,
|
||
certificate_productions_count_since = NOW(),
|
||
updated_at = NOW()
|
||
WHERE id = $2::int;
|
||
"#;
|
||
|
||
/// Zertifikat + `event_user_id` für WebSocket (`COALESCE(user_id, id)` wie bei `QUERY_GET_PRODUCTION_CERTIFICATE_INPUT_ROWS`).
|
||
pub const QUERY_GET_FALUKANT_USER_CERT_AND_EVENT: &str = r#"
|
||
SELECT COALESCE(certificate, 1)::int AS certificate,
|
||
COALESCE(user_id, id)::int AS event_user_id
|
||
FROM falukant_data.falukant_user
|
||
WHERE id = $1::int;
|
||
"#;
|
||
|