Implement political benefits management in FalukantFamilyWorker and SQL: Introduced a new structure for handling lover relationships, including political slots and reputation ticks. Updated SQL queries to support political benefits, ensuring proper handling of appointments and reputation gains. Enhanced the FalukantFamilyWorker logic to manage free political slots and maintain relationships effectively. Improved documentation for clarity on the new political benefits features and their integration into the existing system.
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 2m55s
All checks were successful
Deploy yourpart (blue-green) / deploy (push) Successful in 2m55s
This commit is contained in:
43
migrations/012_falukant_political_benefits_daemon.sql
Normal file
43
migrations/012_falukant_political_benefits_daemon.sql
Normal file
@@ -0,0 +1,43 @@
|
||||
-- Daemon: Amtsvorteile (reputation_periodic Ticks, optional Ernennungs-Ablauf)
|
||||
-- Voraussetzung: Backend-Seeds `falukant_predefine.political_office_benefit` + ggf. `falukant_type.political_office_benefit_type`
|
||||
|
||||
CREATE TABLE IF NOT EXISTS falukant_data.political_benefit_last_tick (
|
||||
id SERIAL PRIMARY KEY,
|
||||
character_id INTEGER NOT NULL
|
||||
REFERENCES falukant_data.character (id) ON DELETE CASCADE,
|
||||
political_office_benefit_id INTEGER NOT NULL,
|
||||
last_tick_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ticks_count INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT uq_political_benefit_last_tick UNIQUE (character_id, political_office_benefit_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_political_benefit_last_tick_character
|
||||
ON falukant_data.political_benefit_last_tick (character_id);
|
||||
|
||||
COMMENT ON TABLE falukant_data.political_benefit_last_tick IS
|
||||
'Letzter reputation_periodic-Tick pro (Charakter × Benefit-Zeile aus political_office_benefit); Daemon: YpDaemon political_benefits.rs';
|
||||
|
||||
-- Optional: Spieler-Ernennungen (Backend legt Zeilen an; Daemon setzt nur abgelaufen)
|
||||
CREATE TABLE IF NOT EXISTS falukant_data.political_appointment (
|
||||
id SERIAL PRIMARY KEY,
|
||||
appointer_character_id INTEGER NOT NULL
|
||||
REFERENCES falukant_data.character (id) ON DELETE CASCADE,
|
||||
target_character_id INTEGER
|
||||
REFERENCES falukant_data.character (id) ON DELETE SET NULL,
|
||||
office_type_id INTEGER NOT NULL
|
||||
REFERENCES falukant_type.political_office_type (id),
|
||||
region_id INTEGER NOT NULL
|
||||
REFERENCES falukant_data.region (id),
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ,
|
||||
completed_office_id INTEGER
|
||||
REFERENCES falukant_data.political_office (id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_political_appointment_status_expires
|
||||
ON falukant_data.political_appointment (status, expires_at);
|
||||
|
||||
COMMENT ON TABLE falukant_data.political_appointment IS
|
||||
'Ernennungen (Backend); Daemon markiert pending → expired wenn expires_at überschritten';
|
||||
@@ -19,3 +19,13 @@ Nur nötig, wenn **`001`** bereits mit den **alten** Spaltennamen (`consecutive_
|
||||
**Backend (YourPart3):** Beim Anlegen einer `lover`-Beziehung `relationship_state` erzeugen; Ehezufriedenheit liegt auf **`relationship`** (married / engaged / wooing); Idempotenzfelder `last_daily_processed_at` / `last_monthly_processed_at` werden vom Daemon gesetzt.
|
||||
|
||||
Ohne passende Spalten (`last_daily_processed_at`) bleibt der Family-Worker inaktiv.
|
||||
|
||||
## `012_falukant_political_benefits_daemon.sql`
|
||||
|
||||
Tabellen **`political_benefit_last_tick`** und optional **`political_appointment`** für den **`PoliticsWorker`** / Modul `political_benefits.rs`:
|
||||
|
||||
- **`reputation_periodic`**: Ticks mit Persistenz (benötigt Backend-Seeds `falukant_predefine.political_office_benefit` mit JSON-Feldern `tr` oder `benefitType`, `gain`, `intervalDays`).
|
||||
- **`free_lover_slots`**: Summe `count` im Liebschafts-Monatstick (Daemon), max. 5.
|
||||
- **Ernennungen**: Daemon setzt nur `pending` → `expired`, wenn `expires_at` überschritten (Anlage durch Backend-API).
|
||||
|
||||
Die Join-Spalte auf `political_office_benefit` heißt im Repo **`political_office_type_id`** — falls das Sequelize-Modell abweicht, SQL in `src/worker/sql.rs` anpassen.
|
||||
|
||||
@@ -720,13 +720,35 @@ impl FalukantFamilyWorker {
|
||||
conn.prepare("mar_sub", QUERY_MARRIAGE_SUBTRACT_SATISFACTION)?;
|
||||
conn.prepare("mark_inst", QUERY_MARK_LOVER_INSTALLMENT_AT)?;
|
||||
|
||||
let mut notify: HashSet<i32> = HashSet::new();
|
||||
struct LoverInst {
|
||||
rel_id: i32,
|
||||
c1: i32,
|
||||
c2: i32,
|
||||
lover_role: String,
|
||||
maintenance_level: i32,
|
||||
status_fit: i32,
|
||||
base: i32,
|
||||
title1: String,
|
||||
title2: String,
|
||||
u1: Option<i32>,
|
||||
u2: Option<i32>,
|
||||
affection: i32,
|
||||
visibility: i32,
|
||||
discretion: i32,
|
||||
consec: i32,
|
||||
scandal_extra: i32,
|
||||
payer_cid: Option<i32>,
|
||||
free_political_slot: bool,
|
||||
}
|
||||
|
||||
let mut items: Vec<LoverInst> = Vec::new();
|
||||
for r in lover_rows {
|
||||
let rel_id = parse_i32(&r, "rel_id", -1);
|
||||
if rel_id < 0 {
|
||||
continue;
|
||||
}
|
||||
let c1 = parse_i32(&r, "c1", -1);
|
||||
let c2 = parse_i32(&r, "c2", -1);
|
||||
let lover_role = r.get("lover_role").cloned().unwrap_or_default();
|
||||
let maintenance_level = parse_i32(&r, "maintenance_level", 50);
|
||||
let status_fit = parse_i32(&r, "status_fit", 0);
|
||||
@@ -741,6 +763,83 @@ impl FalukantFamilyWorker {
|
||||
}
|
||||
let title1 = r.get("title1_tr").cloned().unwrap_or_default();
|
||||
let title2 = r.get("title2_tr").cloned().unwrap_or_default();
|
||||
let u1 = parse_opt_i32(&r, "user1_id");
|
||||
let u2 = parse_opt_i32(&r, "user2_id");
|
||||
let payer = u1.or(u2).filter(|x| *x > 0);
|
||||
let payer_cid = match (u1, u2, payer) {
|
||||
(Some(a), _, Some(p)) if p == a => Some(c1),
|
||||
(_, Some(b), Some(p)) if p == b => Some(c2),
|
||||
_ => None,
|
||||
};
|
||||
items.push(LoverInst {
|
||||
rel_id,
|
||||
c1,
|
||||
c2,
|
||||
lover_role,
|
||||
maintenance_level,
|
||||
status_fit,
|
||||
base,
|
||||
title1,
|
||||
title2,
|
||||
u1,
|
||||
u2,
|
||||
affection: parse_i32(&r, "affection", 50),
|
||||
visibility: parse_i32(&r, "visibility", 0),
|
||||
discretion: parse_i32(&r, "discretion", 50),
|
||||
consec: parse_i32(&r, "months_underfunded", 0),
|
||||
scandal_extra: parse_i32(&r, "scandal_extra_daily_pct", 0),
|
||||
payer_cid,
|
||||
free_political_slot: false,
|
||||
});
|
||||
}
|
||||
|
||||
items.sort_by_key(|x| (x.payer_cid.unwrap_or(-1), x.rel_id));
|
||||
|
||||
let mut free_slots_by_char: HashMap<i32, i32> = HashMap::new();
|
||||
let mut seen_payer: HashSet<i32> = HashSet::new();
|
||||
for it in &items {
|
||||
if let Some(cid) = it.payer_cid {
|
||||
if seen_payer.insert(cid) {
|
||||
if let Ok(n) = super::political_benefits::sum_free_lover_slots(&mut conn, cid) {
|
||||
free_slots_by_char.insert(cid, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut cur_group = -2i32;
|
||||
let mut eligible_ix: usize = 0;
|
||||
for it in &mut items {
|
||||
let gk = it.payer_cid.unwrap_or(-1);
|
||||
if gk != cur_group {
|
||||
cur_group = gk;
|
||||
eligible_ix = 0;
|
||||
}
|
||||
let free_n = it
|
||||
.payer_cid
|
||||
.and_then(|c| free_slots_by_char.get(&c).copied())
|
||||
.unwrap_or(0);
|
||||
let role_ok = matches!(
|
||||
it.lover_role.as_str(),
|
||||
"lover" | "mistress_or_favorite"
|
||||
);
|
||||
if role_ok && it.payer_cid.is_some() {
|
||||
if eligible_ix < free_n as usize {
|
||||
it.free_political_slot = true;
|
||||
}
|
||||
eligible_ix += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut notify: HashSet<i32> = HashSet::new();
|
||||
|
||||
for it in items {
|
||||
let rel_id = it.rel_id;
|
||||
let maintenance_level = it.maintenance_level;
|
||||
let status_fit = it.status_fit;
|
||||
let base = it.base;
|
||||
let title1 = it.title1.clone();
|
||||
let title2 = it.title2.clone();
|
||||
let g = pair_rank_group(&title1, &title2);
|
||||
let rank_m = rank_cost_multiplier(g);
|
||||
let maint_f = 0.6 + (maintenance_level as f64 / 100.0) * 1.2;
|
||||
@@ -750,17 +849,20 @@ impl FalukantFamilyWorker {
|
||||
_ => 1.0,
|
||||
};
|
||||
let cost = ((base as f64) * rank_m * maint_f * sf_m).round() as i32;
|
||||
let installment = ((cost as f64) / 12.0 * 100.0).round() / 100.0;
|
||||
let mut installment = ((cost as f64) / 12.0 * 100.0).round() / 100.0;
|
||||
if it.free_political_slot {
|
||||
installment = 0.0;
|
||||
}
|
||||
|
||||
let u1 = parse_opt_i32(&r, "user1_id");
|
||||
let u2 = parse_opt_i32(&r, "user2_id");
|
||||
let u1 = it.u1;
|
||||
let u2 = it.u2;
|
||||
let payer = u1.or(u2).filter(|x| *x > 0);
|
||||
|
||||
let affection = parse_i32(&r, "affection", 50);
|
||||
let visibility = parse_i32(&r, "visibility", 0);
|
||||
let discretion = parse_i32(&r, "discretion", 50);
|
||||
let mut consec = parse_i32(&r, "months_underfunded", 0);
|
||||
let mut scandal_extra = parse_i32(&r, "scandal_extra_daily_pct", 0);
|
||||
let affection = it.affection;
|
||||
let visibility = it.visibility;
|
||||
let discretion = it.discretion;
|
||||
let mut consec = it.consec;
|
||||
let mut scandal_extra = it.scandal_extra;
|
||||
|
||||
if let Some(uid) = payer {
|
||||
if installment <= 0.0 {
|
||||
@@ -795,7 +897,7 @@ impl FalukantFamilyWorker {
|
||||
],
|
||||
)?;
|
||||
|
||||
for cid in [parse_i32(&r, "c1", 0), parse_i32(&r, "c2", 0)] {
|
||||
for cid in [it.c1, it.c2] {
|
||||
if cid <= 0 {
|
||||
continue;
|
||||
}
|
||||
@@ -805,7 +907,7 @@ impl FalukantFamilyWorker {
|
||||
}
|
||||
|
||||
if visibility >= 40 {
|
||||
for cid in [parse_i32(&r, "c1", 0), parse_i32(&r, "c2", 0)] {
|
||||
for cid in [it.c1, it.c2] {
|
||||
if cid > 0 {
|
||||
let cur = fetch_reputation(&mut conn, cid)?;
|
||||
let s = format!("{:.2}", (cur - 1.0).max(0.0));
|
||||
|
||||
@@ -17,6 +17,7 @@ mod falukant_certificate;
|
||||
mod falukant_servants;
|
||||
mod falukant_debtors;
|
||||
mod falukant_transport_raid;
|
||||
mod political_benefits;
|
||||
mod sql;
|
||||
|
||||
pub use base::Worker;
|
||||
|
||||
171
src/worker/political_benefits.rs
Normal file
171
src/worker/political_benefits.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
//! Politische Amtsvorteile: `reputation_periodic` (täglich), Ablauf offener Ernennungen,
|
||||
//! Hilfsabfrage `free_lover_slots` für Liebschafts-Monatstick.
|
||||
//!
|
||||
//! Voraussetzungen: Migration `012_falukant_political_benefits_daemon.sql`, Backend-Seeds
|
||||
//! `falukant_predefine.political_office_benefit` mit JSON (`tr` oder `benefitType`, `gain`, `intervalDays`, …).
|
||||
|
||||
use crate::db::{ConnectionPool, DbError, Row};
|
||||
use crate::message_broker::MessageBroker;
|
||||
|
||||
use crate::worker::sql::{
|
||||
QUERY_POLITICAL_APPOINTMENT_EXPIRE_PENDING, QUERY_POLITICAL_APPOINTMENT_SCHEMA_READY,
|
||||
QUERY_POLITICAL_BENEFIT_DAEMON_SCHEMA_READY, QUERY_POLITICAL_REPUTATION_APPLY_GAIN,
|
||||
QUERY_POLITICAL_REPUTATION_TICK_ROWS, QUERY_POLITICAL_REPUTATION_TICK_UPSERT,
|
||||
QUERY_SUM_FREE_LOVER_SLOTS_FOR_CHARACTER,
|
||||
};
|
||||
|
||||
fn parse_i32(row: &Row, key: &str, default: i32) -> i32 {
|
||||
row.get(key)
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn parse_f64_from_row(row: &Row, key: &str) -> f64 {
|
||||
row.get(key)
|
||||
.and_then(|v| v.parse::<f64>().ok())
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn daemon_schema_ready(pool: &ConnectionPool) -> bool {
|
||||
let mut conn = match pool.get() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return false,
|
||||
};
|
||||
if conn
|
||||
.prepare("pb_schema", QUERY_POLITICAL_BENEFIT_DAEMON_SCHEMA_READY)
|
||||
.is_err()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let rows = match conn.execute("pb_schema", &[]) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return false,
|
||||
};
|
||||
rows.first()
|
||||
.and_then(|r| r.get("ready"))
|
||||
.map(|v| v == "true" || v == "t")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn appointment_schema_ready(pool: &ConnectionPool) -> bool {
|
||||
let mut conn = match pool.get() {
|
||||
Ok(c) => c,
|
||||
Err(_) => return false,
|
||||
};
|
||||
if conn
|
||||
.prepare("pa_schema", QUERY_POLITICAL_APPOINTMENT_SCHEMA_READY)
|
||||
.is_err()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let rows = match conn.execute("pa_schema", &[]) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return false,
|
||||
};
|
||||
rows.first()
|
||||
.and_then(|r| r.get("ready"))
|
||||
.map(|v| v == "true" || v == "t")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Täglich: Ansehen gemäß `reputation_periodic` + Upsert `political_benefit_last_tick` + Status-Push.
|
||||
pub fn run_reputation_periodic_ticks(pool: &ConnectionPool, broker: &MessageBroker) -> Result<(), DbError> {
|
||||
if !daemon_schema_ready(pool) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("rep_tick_rows", QUERY_POLITICAL_REPUTATION_TICK_ROWS)
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] prepare rep_tick_rows: {e}")))?;
|
||||
conn.prepare("rep_tick_upsert", QUERY_POLITICAL_REPUTATION_TICK_UPSERT)
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] prepare rep_tick_upsert: {e}")))?;
|
||||
conn.prepare("rep_apply", QUERY_POLITICAL_REPUTATION_APPLY_GAIN)
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] prepare rep_apply: {e}")))?;
|
||||
|
||||
let rows = conn
|
||||
.execute("rep_tick_rows", &[])
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] exec rep_tick_rows: {e}")))?;
|
||||
|
||||
let mut notified: std::collections::HashSet<i32> = std::collections::HashSet::new();
|
||||
|
||||
for row in rows {
|
||||
let benefit_id = parse_i32(&row, "benefit_id", -1);
|
||||
let character_id = parse_i32(&row, "character_id", -1);
|
||||
let gain = parse_f64_from_row(&row, "gain");
|
||||
let user_id = parse_i32(&row, "falukant_user_id", -1);
|
||||
|
||||
if benefit_id < 1 || character_id < 1 || gain <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let gain_str = format!("{:.2}", gain);
|
||||
|
||||
conn.execute("rep_apply", &[&gain_str, &character_id])
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] rep_apply cid={character_id}: {e}")))?;
|
||||
|
||||
conn.execute(
|
||||
"rep_tick_upsert",
|
||||
&[&character_id, &benefit_id],
|
||||
)
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] rep_tick_upsert: {e}")))?;
|
||||
|
||||
eprintln!(
|
||||
"[PoliticalBenefits] characterId={} officeBenefitId={} action=reputation_tick gain={}",
|
||||
character_id, benefit_id, gain_str
|
||||
);
|
||||
|
||||
if user_id > 0 {
|
||||
notified.insert(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
for uid in notified {
|
||||
let msg = format!(r#"{{"event":"falukantUpdateStatus","user_id":{}}}"#, uid);
|
||||
broker.publish(msg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Täglich: `pending` → `expired`, wenn `expires_at` überschritten.
|
||||
pub fn expire_political_appointments(pool: &ConnectionPool) -> Result<(), DbError> {
|
||||
if !appointment_schema_ready(pool) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut conn = pool
|
||||
.get()
|
||||
.map_err(|e| DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
|
||||
|
||||
conn.prepare("pa_expire", QUERY_POLITICAL_APPOINTMENT_EXPIRE_PENDING)
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] prepare pa_expire: {e}")))?;
|
||||
conn.execute("pa_expire", &[])
|
||||
.map_err(|e| DbError::new(format!("[PoliticalBenefits] exec pa_expire: {e}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Summe freier Liebschafts-Slots (max. 5) für einen Charakter mit aktivem politischen Amt.
|
||||
pub fn sum_free_lover_slots(
|
||||
conn: &mut crate::db::DbConnection,
|
||||
character_id: i32,
|
||||
) -> Result<i32, DbError> {
|
||||
if character_id < 1 {
|
||||
return Ok(0);
|
||||
}
|
||||
if conn
|
||||
.prepare("free_lover_slots", QUERY_SUM_FREE_LOVER_SLOTS_FOR_CHARACTER)
|
||||
.is_err()
|
||||
{
|
||||
return Ok(0);
|
||||
}
|
||||
let rows = conn.execute("free_lover_slots", &[&character_id])?;
|
||||
Ok(rows
|
||||
.first()
|
||||
.and_then(|r| r.get("free_slots"))
|
||||
.and_then(|v| v.parse::<i32>().ok())
|
||||
.unwrap_or(0)
|
||||
.max(0))
|
||||
}
|
||||
@@ -222,6 +222,13 @@ impl PoliticsWorker {
|
||||
// Lösch- und Besetzungsvorgängen ausgeführt.
|
||||
Self::trim_excess_offices_global(pool)?;
|
||||
|
||||
if let Err(e) = super::political_benefits::run_reputation_periodic_ticks(pool, broker) {
|
||||
eprintln!("[PoliticsWorker] run_reputation_periodic_ticks: {e}");
|
||||
}
|
||||
if let Err(e) = super::political_benefits::expire_political_appointments(pool) {
|
||||
eprintln!("[PoliticsWorker] expire_political_appointments: {e}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1289,6 +1289,102 @@ SELECT elig.user_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();
|
||||
"#;
|
||||
|
||||
/// 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
|
||||
|
||||
Reference in New Issue
Block a user