Merge remote-tracking branch 'origin/main' into main
Resolve config.py conflict by taking Danil's PR #39 schedule content (teacher names/rooms, per-week Thursday split) as the source of truth, per explicit user decision, with two fixes on top: - UPPER_WEEK_IS_ODD restored to False (current ISO-even week is upper, confirmed by the user) - added the missing Saturday entry under LESSON_SCHEDULE["lower"] (PR #39 only defined it under "upper", which would have silently dropped the Saturday philosophy lecture on lower weeks)
This commit is contained in:
commit
e3c6e1a4df
7 changed files with 267 additions and 235 deletions
|
|
@ -63,6 +63,7 @@ docker compose down
|
||||||
- `/bet [спорт] <номер> <исход> <ставка>` — поставить см на матч.
|
- `/bet [спорт] <номер> <исход> <ставка>` — поставить см на матч.
|
||||||
- `/mybets` — мои активные ставки.
|
- `/mybets` — мои активные ставки.
|
||||||
- `/svodka` — сводка СВО.
|
- `/svodka` — сводка СВО.
|
||||||
|
- `/uwu [теги]` — поиск случайной пикчи/гифки на e621 (по умолчанию sfw, можно указать свои теги).
|
||||||
|
|
||||||
## Экономика ZOV OS 💰
|
## Экономика ZOV OS 💰
|
||||||
|
|
||||||
|
|
|
||||||
185
config.py
185
config.py
|
|
@ -87,89 +87,116 @@ PAIR_TIMES = {
|
||||||
5: (make_time(15, 50), make_time(17, 25)),
|
5: (make_time(15, 50), make_time(17, 25)),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Расписание 2 курс, гр. 27 (25ВТ-09.03.03.01-о2), осень 2026-27.
|
|
||||||
# Источник: Raspis_osen_2_kurs_2026-27_g.pdf (черновик, водяной знак "Проект", возможны изменения).
|
|
||||||
# Разделения на верхнюю/нижнюю неделю в источнике нет — обе недели одинаковые.
|
|
||||||
_MON = [
|
|
||||||
{"pair": 2, "title": "Экономика и право (практика), Индустриев М.А. до 06.11 / Васильева Е.В. с 07.11, ауд. 504"},
|
|
||||||
{"pair": 3, "title": "Экономика и право (лекция), Туманян Ю.Р. до 06.11 / Васильева Е.В. с 07.11, ауд. 510"},
|
|
||||||
{"pair": 4, "title": "Геоинформационные системы (лекция), Ушканова Е.В., ауд. 403а (НИИМ и ПМ)"},
|
|
||||||
]
|
|
||||||
_TUE = [
|
|
||||||
{"pair": 3, "title": "Специальные разделы математики (практика), Вакулов Б.Г., ауд. 504"},
|
|
||||||
{"pair": 4, "title": "Геоинформационные системы (лаб), Ушканова Е.В., ауд. 403а (НИИМ и ПМ)"},
|
|
||||||
]
|
|
||||||
_WED = [
|
|
||||||
{"pair": 2, "title": "Технология программирования (лекция), Губский Д.С., ауд. 510"},
|
|
||||||
{"pair": 3, "title": "1 подгруппа: Основы функционирования вычислительной техники (лаб), Дацюк О.В., ауд. 606 | 2 подгруппа: Геоинформационные системы (лаб), Ушканова Е.В., ауд. 403а (НИИМ и ПМ)"},
|
|
||||||
{"pair": 4, "title": "1 подгруппа: Технология программирования (лаб), Губский Д.С., ауд. 609 | 2 подгруппа: Технология программирования (лаб), Иванова И.Н., ауд. 609а"},
|
|
||||||
{"pair": 5, "title": "Основы функционирования вычислительной техники (лаб), Дацюк О.В., ауд. 606"},
|
|
||||||
]
|
|
||||||
_THU = [
|
|
||||||
{"pair": 2, "title": "Основы функционирования вычислительной техники (лекция), Дацюк О.В., ауд. 510"},
|
|
||||||
{"pair": 3, "title": "1 подгруппа: Основы функционирования вычислительной техники (практика), Дацюк О.В., ауд. 818 | 2 подгруппа: Специальные разделы математики (практика), Вакулов Б.Г., ауд. 507"},
|
|
||||||
{"pair": 4, "title": "Специальные разделы математики (лекция), Каплицкий В.М., ауд. 510"},
|
|
||||||
]
|
|
||||||
_FRI = [
|
|
||||||
{"pair": None, "title": "Иностранный язык (время уточняется)"},
|
|
||||||
]
|
|
||||||
_SAT = [
|
|
||||||
{"pair": 1, "title": "Философия (лекция, совместно с гр. 26), Новохатько А.Г., MS Teams"},
|
|
||||||
]
|
|
||||||
|
|
||||||
_LESSON_WEEK = {
|
|
||||||
0: _MON,
|
|
||||||
1: _TUE,
|
|
||||||
2: _WED,
|
|
||||||
3: _THU,
|
|
||||||
4: _FRI,
|
|
||||||
5: _SAT,
|
|
||||||
}
|
|
||||||
|
|
||||||
LESSON_SCHEDULE = {
|
LESSON_SCHEDULE = {
|
||||||
"upper": _LESSON_WEEK,
|
"upper": {
|
||||||
"lower": _LESSON_WEEK,
|
0: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Экономика и право (практика), пр. Индустриев М.А. (до 6.11.25) / Васильева Е.В. (с 7.11.25), а. 504"},
|
||||||
|
{"pair": 3, "title": "Экономика и право (лекция), пр. Туманян Ю.Р. (до 6.11.25) / Васильева Е.В. (с 7.11.25), а. 510"},
|
||||||
|
{"pair": 4, "title": "Геоинформационные системы (лекция), пр. Ушкалова Е.В., а. 403а (НИИМ и ПМ)"},
|
||||||
|
],
|
||||||
|
1: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Нет пары"},
|
||||||
|
{"pair": 3, "title": "Специальные разделы математики (практика), пр. Вакулин Б.Г., а. 504"},
|
||||||
|
{"pair": 4, "title": "1-я подгруппа: Геоинформационные системы (лаб), пр. Ушкалова Е.В., а. 403а (НИИМ и ПМ)"},
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Технологии программирования (лекция), пр. Губский Д.С., а. 510"},
|
||||||
|
{"pair": 3, "title": "1-я подгруппа: Основы функционирования ВТ (лаб), пр. Дядик О.В., а. 606. 2-я подгруппа: Геоинформационные системы (лаб), пр. Ушкалова Е.В., а. 403а (НИИМ и ПМ)"},
|
||||||
|
{"pair": 4, "title": "1-я подгруппа: Технологии программирования (лаб), пр. Губский Д.С., а. 609. 2-я подгруппа: Технологии программирования (лаб), пр. Иванова Н.Н., а. 609 а"},
|
||||||
|
{"pair": 5, "title": "2-я подгруппа: Основы функционирования вычислительной техники (лаб), пр. Дядик О.В., а. 606"},
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Специальные разделы математики (прак), пр. Вакулин Б.Г., а. 502"},
|
||||||
|
{"pair": 3, "title": "Основы функционирования вычислительной техники (прак), пр. Дядик О.В., а. 818"},
|
||||||
|
{"pair": 4, "title": "Специальные разделы математики (лекция), пр. Котлярский В.М., а. 510"},
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
{"pair": None, "title": "Иностранный язык"},
|
||||||
|
],
|
||||||
|
5: [
|
||||||
|
{"pair": 1, "title": "Философия (лекция), пр. Новосёлова А.Г., MS Teams"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"lower": {
|
||||||
|
0: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Экономика и право (практика), пр. Индустриев М.А. (до 6.11.25) / Васильева Е.В. (с 7.11.25), а. 504"},
|
||||||
|
{"pair": 3, "title": "Экономика и право (лекция), пр. Туманян Ю.Р. (до 6.11.25) / Васильева Е.В. (с 7.11.25), а. 510"},
|
||||||
|
{"pair": 4, "title": "Геоинформационные системы (лекция), пр. Ушкалова Е.В., а. 403а (НИИМ и ПМ)"},
|
||||||
|
],
|
||||||
|
1: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Нет пары"},
|
||||||
|
{"pair": 3, "title": "Специальные разделы математики (практика), пр. Вакулин Б.Г., а. 504"},
|
||||||
|
{"pair": 4, "title": "1-я подгруппа: Геоинформационные системы (лаб), пр. Ушкалова Е.В., а. 403а (НИИМ и ПМ)"},
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Технологии программирования (лекция), пр. Губский Д.С., а. 510"},
|
||||||
|
{"pair": 3, "title": "1-я подгруппа: Основы функционирования ВТ (лаб), пр. Дядик О.В., а. 606. 2-я подгруппа: Геоинформационные системы (лаб), пр. Ушкалова Е.В., а. 403а (НИИМ и ПМ)"},
|
||||||
|
{"pair": 4, "title": "1-я подгруппа: Технологии программирования (лаб), пр. Губский Д.С., а. 609. 2-я подгруппа: Технологии программирования (лаб), пр. Иванова Н.Н., а. 609 а"},
|
||||||
|
{"pair": 5, "title": "2-я подгруппа: Основы функционирования вычислительной техники (лаб), пр. Дядик О.В., а. 606"},
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
{"pair": 1, "title": "Нет пары"},
|
||||||
|
{"pair": 2, "title": "Основы функционирования вычислительной техники (лекция), пр. Дядик О.В., а. 514"},
|
||||||
|
{"pair": 3, "title": "Нет пары"},
|
||||||
|
{"pair": 4, "title": "Специальные разделы математики (лекция), пр. Котлярский В.М., а. 510"},
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
{"pair": None, "title": "Иностранный язык"},
|
||||||
|
],
|
||||||
|
# В PR отсутствовали пятница/суббота на нижней неделе — этих пар нет
|
||||||
|
# только в исходном коммите; иняз и философия по субботам идут
|
||||||
|
# независимо от чётности недели, так что дублируем из "upper".
|
||||||
|
5: [
|
||||||
|
{"pair": 1, "title": "Философия (лекция), пр. Новосёлова А.Г., MS Teams"},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# SCHEDULE — тайминг перекуров для /break, построен из фактических пар в
|
|
||||||
# _LESSON_WEEK, а не задаётся руками, чтобы не рассинхронизироваться при
|
|
||||||
# правках расписания (раньше здесь была отдельная захардкоженная таблица,
|
|
||||||
# которая не обновилась вместе с LESSON_SCHEDULE и врала на днях с занятиями).
|
|
||||||
# Каждая пара (95 мин) состоит из двух академ. часов по 45 мин с 5-минутным
|
|
||||||
# перекуром между ними; между парами перекур — фактический зазор по PAIR_TIMES.
|
|
||||||
def _time_to_minutes(t: time) -> int:
|
|
||||||
return t.hour * 60 + t.minute
|
|
||||||
|
|
||||||
|
|
||||||
def _minutes_to_time(m: int) -> time:
|
|
||||||
return make_time(m // 60, m % 60)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_day_schedule(pairs: list[int]) -> list[tuple[time, time, int | None]]:
|
|
||||||
entries = []
|
|
||||||
for idx, pair in enumerate(pairs):
|
|
||||||
start_t, end_t = PAIR_TIMES[pair]
|
|
||||||
start_m = _time_to_minutes(start_t)
|
|
||||||
end_m = _time_to_minutes(end_t)
|
|
||||||
mid_m = start_m + 45
|
|
||||||
second_start_m = mid_m + 5
|
|
||||||
entries.append((start_t, _minutes_to_time(mid_m), 5))
|
|
||||||
if idx == len(pairs) - 1:
|
|
||||||
entries.append((_minutes_to_time(second_start_m), end_t, None))
|
|
||||||
else:
|
|
||||||
next_start_t, _ = PAIR_TIMES[pairs[idx + 1]]
|
|
||||||
gap = _time_to_minutes(next_start_t) - end_m
|
|
||||||
entries.append((_minutes_to_time(second_start_m), end_t, gap))
|
|
||||||
return entries
|
|
||||||
|
|
||||||
|
|
||||||
def _pairs_for_day(day_lessons: list[dict]) -> list[int]:
|
|
||||||
return sorted({item["pair"] for item in day_lessons if item.get("pair") is not None})
|
|
||||||
|
|
||||||
|
|
||||||
SCHEDULE = {
|
SCHEDULE = {
|
||||||
day: _build_day_schedule(_pairs_for_day(lessons))
|
0: [ # Понедельник
|
||||||
for day, lessons in _LESSON_WEEK.items()
|
(make_time(9, 50), make_time(10, 35), 5),
|
||||||
|
(make_time(10, 40), make_time(11, 25), 30),
|
||||||
|
(make_time(11, 55), make_time(12, 40), 5),
|
||||||
|
(make_time(12, 45), make_time(13, 30), 15),
|
||||||
|
(make_time(13, 45), make_time(14, 30), 5),
|
||||||
|
(make_time(14, 35), make_time(15, 20), None),
|
||||||
|
],
|
||||||
|
1: [ # Вторник
|
||||||
|
(make_time(11, 55), make_time(12, 40), 5),
|
||||||
|
(make_time(12, 45), make_time(13, 30), 15),
|
||||||
|
(make_time(13, 45), make_time(14, 30), 5),
|
||||||
|
(make_time(14, 35), make_time(15, 20), None),
|
||||||
|
],
|
||||||
|
2: [ # Среда
|
||||||
|
(make_time(9, 50), make_time(10, 35), 5),
|
||||||
|
(make_time(10, 40), make_time(11, 25), 30),
|
||||||
|
(make_time(11, 55), make_time(12, 40), 5),
|
||||||
|
(make_time(12, 45), make_time(13, 30), 15),
|
||||||
|
(make_time(13, 45), make_time(14, 30), 5),
|
||||||
|
(make_time(14, 35), make_time(15, 20), 30),
|
||||||
|
(make_time(15, 50), make_time(16, 35), 5),
|
||||||
|
(make_time(16, 40), make_time(17, 25), None),
|
||||||
|
],
|
||||||
|
3: [ # Четверг
|
||||||
|
(make_time(9, 50), make_time(10, 35), 5),
|
||||||
|
(make_time(10, 40), make_time(11, 25), 30),
|
||||||
|
(make_time(11, 55), make_time(12, 40), 5),
|
||||||
|
(make_time(12, 45), make_time(13, 30), 15),
|
||||||
|
(make_time(13, 45), make_time(14, 30), 5),
|
||||||
|
(make_time(14, 35), make_time(15, 20), None),
|
||||||
|
],
|
||||||
|
4: [], # Пятница
|
||||||
|
5: [ # Суббота
|
||||||
|
(make_time(8, 0), make_time(8, 45), 5),
|
||||||
|
(make_time(8, 50), make_time(9, 35), None),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Остальное ---
|
# --- Остальное ---
|
||||||
|
|
|
||||||
|
|
@ -286,26 +286,26 @@ async def get_match_by_index(sport_alias: str, index: int) -> dict | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def place_bet(user_id: int, match: dict, sport: str, chosen_team: str, amount: float) -> str:
|
def place_bet(user_id: int, match: dict, sport: str, chosen_team: str, amount: float) -> tuple[bool, str]:
|
||||||
current = get_user_length(user_id)
|
current = get_user_length(user_id)
|
||||||
if current is None:
|
if current is None:
|
||||||
return "Сначала заведи счёт через /penis, братуха."
|
return False, "Сначала заведи счёт через /penis, братуха."
|
||||||
if current <= 0:
|
if current <= 0:
|
||||||
return "🚫 С кредитом ставки не принимаем."
|
return False, "🚫 С кредитом ставки не принимаем."
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
return "Ставка должна быть больше нуля."
|
return False, "Ставка должна быть больше нуля."
|
||||||
if amount > config.BET_MAX_AMOUNT:
|
if amount > config.BET_MAX_AMOUNT:
|
||||||
return f"Максимальная ставка — {config.BET_MAX_AMOUNT} см."
|
return False, f"Максимальная ставка — {config.BET_MAX_AMOUNT} см."
|
||||||
if amount > current:
|
if round(amount, 2) > round(current, 2):
|
||||||
return f"У тебя {current:.1f} см, а ставишь {amount:.1f}. Не хватает, фраер."
|
return False, f"У тебя {current:.1f} см, а ставишь {amount:.1f}. Не хватает, фраер."
|
||||||
|
|
||||||
team_odds = match["odds"].get(chosen_team)
|
team_odds = match["odds"].get(chosen_team)
|
||||||
if team_odds is None:
|
if team_odds is None:
|
||||||
return f"Исход не найден. Доступные: {describe_match_outcomes(match)}"
|
return False, f"Исход не найден. Доступные: {describe_match_outcomes(match)}"
|
||||||
|
|
||||||
new_length = update_user_length(user_id, -amount)
|
new_length = update_user_length(user_id, -amount)
|
||||||
if new_length is None:
|
if new_length is None:
|
||||||
return "Ошибка БД."
|
return False, "Ошибка БД."
|
||||||
|
|
||||||
now_ts = int(_time.time())
|
now_ts = int(_time.time())
|
||||||
try:
|
try:
|
||||||
|
|
@ -323,12 +323,11 @@ def place_bet(user_id: int, match: dict, sport: str, chosen_team: str, amount: f
|
||||||
except psycopg2.Error:
|
except psycopg2.Error:
|
||||||
logger.exception("Failed to place bet")
|
logger.exception("Failed to place bet")
|
||||||
update_user_length(user_id, amount)
|
update_user_length(user_id, amount)
|
||||||
return "Ошибка БД."
|
return False, "Ошибка БД."
|
||||||
|
|
||||||
potential = round(amount * team_odds, 1)
|
potential = round(amount * team_odds, 1)
|
||||||
return (
|
return True, (
|
||||||
f"✅ Ставка принята!\n"
|
f"✅ Ставка принята!\n"
|
||||||
f"🏟 {match['home']} vs {match['away']}\n"
|
|
||||||
f"📌 {chosen_team} (x{team_odds:.2f})\n"
|
f"📌 {chosen_team} (x{team_odds:.2f})\n"
|
||||||
f"💰 Ставка: {amount:.1f} см\n"
|
f"💰 Ставка: {amount:.1f} см\n"
|
||||||
f"🎯 Возможный выигрыш: {potential:.1f} см\n"
|
f"🎯 Возможный выигрыш: {potential:.1f} см\n"
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ def play_casino(user_id: int, bet: float) -> str:
|
||||||
return f"Максимальная ставка — {config.CASINO_MAX_BET} см, не жадничай."
|
return f"Максимальная ставка — {config.CASINO_MAX_BET} см, не жадничай."
|
||||||
if bet <= 0:
|
if bet <= 0:
|
||||||
return "Ставка должна быть больше нуля, фраер."
|
return "Ставка должна быть больше нуля, фраер."
|
||||||
if bet > current:
|
if round(bet, 2) > round(current, 2):
|
||||||
return f"У тебя {current:.1f} см, а ставишь {bet:.1f}. Столько нет, фраер."
|
return f"У тебя {current:.1f} см, а ставишь {bet:.1f}. Столько нет, фраер."
|
||||||
|
|
||||||
reels, matches = spin_slots()
|
reels, matches = spin_slots()
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,8 @@ class EconomyManager:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from_balance = self.get_user_balance(from_user_id)
|
from_balance = self.get_user_balance(from_user_id)
|
||||||
if amount > from_balance * 0.2 or from_balance < amount:
|
limit = round(from_balance * 0.2, 2)
|
||||||
|
if round(amount, 2) > limit or round(from_balance, 2) < round(amount, 2):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
|
|
@ -201,15 +202,17 @@ class EconomyManager:
|
||||||
logger.info(f"Transfer: {amount} from user {from_user_id} to user {to_user_id}")
|
logger.info(f"Transfer: {amount} from user {from_user_id} to user {to_user_id}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_deposit(self, user_id: int, amount: float, days: int) -> bool:
|
def create_deposit(self, user_id: int, amount: float, days: int) -> tuple[bool, str]:
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
return False
|
return False, "Сумма вклада должна быть больше нуля."
|
||||||
|
|
||||||
balance = self.get_user_balance(user_id)
|
balance = self.get_user_balance(user_id)
|
||||||
min_deposit = float(self.get_setting('deposit_min_amount'))
|
min_deposit = float(self.get_setting('deposit_min_amount'))
|
||||||
|
|
||||||
if amount < min_deposit or balance < amount:
|
if round(amount, 2) < round(min_deposit, 2):
|
||||||
return False
|
return False, f"Минимальная сумма вклада — {min_deposit:.1f} см. Твой баланс: {balance:.1f} см."
|
||||||
|
if round(balance, 2) < round(amount, 2):
|
||||||
|
return False, f"Недостаточно средств. Твой баланс: {balance:.1f} см, а сумма вклада: {amount:.1f} см."
|
||||||
|
|
||||||
central_bank_rate = float(self.get_setting('central_bank_rate'))
|
central_bank_rate = float(self.get_setting('central_bank_rate'))
|
||||||
matures_at = datetime.now() + timedelta(days=days)
|
matures_at = datetime.now() + timedelta(days=days)
|
||||||
|
|
@ -230,7 +233,7 @@ class EconomyManager:
|
||||||
''', (user_id, amount, central_bank_rate, matures_at))
|
''', (user_id, amount, central_bank_rate, matures_at))
|
||||||
|
|
||||||
logger.info(f"Deposit created: user {user_id}, amount {amount}, rate {central_bank_rate}%")
|
logger.info(f"Deposit created: user {user_id}, amount {amount}, rate {central_bank_rate}%")
|
||||||
return True
|
return True, "Вклад успешно открыт!"
|
||||||
|
|
||||||
def create_loan(self, user_id: int, amount: float, days: int) -> bool:
|
def create_loan(self, user_id: int, amount: float, days: int) -> bool:
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
|
|
@ -239,7 +242,7 @@ class EconomyManager:
|
||||||
max_loan = float(self.get_setting('loan_max_amount'))
|
max_loan = float(self.get_setting('loan_max_amount'))
|
||||||
loan_rate = float(self.get_setting('loan_interest_rate'))
|
loan_rate = float(self.get_setting('loan_interest_rate'))
|
||||||
|
|
||||||
if amount > max_loan:
|
if round(amount, 2) > round(max_loan, 2):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
|
|
@ -251,13 +254,13 @@ class EconomyManager:
|
||||||
(user_id,),
|
(user_id,),
|
||||||
)
|
)
|
||||||
outstanding = float(cur.fetchone()['total'])
|
outstanding = float(cur.fetchone()['total'])
|
||||||
if outstanding + amount > max_loan:
|
if round(outstanding + amount, 2) > round(max_loan, 2):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# ЦБ должен иметь достаточно капитала
|
# ЦБ должен иметь достаточно капитала
|
||||||
cur.execute("SELECT capital FROM central_bank WHERE id=1")
|
cur.execute("SELECT capital FROM central_bank WHERE id=1")
|
||||||
cb_capital = float(cur.fetchone()['capital'])
|
cb_capital = float(cur.fetchone()['capital'])
|
||||||
if cb_capital < amount:
|
if round(cb_capital, 2) < round(amount, 2):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
due_at = datetime.now() + timedelta(days=days)
|
due_at = datetime.now() + timedelta(days=days)
|
||||||
|
|
@ -293,10 +296,10 @@ class EconomyManager:
|
||||||
if not loan or loan['is_repaid']:
|
if not loan or loan['is_repaid']:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
total_repayment = loan['amount'] * (1 + loan['interest_rate'] / 100)
|
total_repayment = round(loan['amount'] * (1 + loan['interest_rate'] / 100), 2)
|
||||||
|
|
||||||
balance = self.get_user_balance(user_id)
|
balance = self.get_user_balance(user_id)
|
||||||
if balance < total_repayment:
|
if round(balance, 2) < total_repayment:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
wcur = conn.cursor()
|
wcur = conn.cursor()
|
||||||
|
|
@ -479,7 +482,7 @@ class EconomyManager:
|
||||||
logger.info(f"Central bank updated: {capital_change} ({transaction_type})")
|
logger.info(f"Central bank updated: {capital_change} ({transaction_type})")
|
||||||
|
|
||||||
def emit_money(self, amount: float, reason: str = "") -> bool:
|
def emit_money(self, amount: float, reason: str = "") -> bool:
|
||||||
if self.get_central_bank_stats()['capital'] < amount:
|
if round(self.get_central_bank_stats()['capital'], 2) < round(amount, 2):
|
||||||
return False
|
return False
|
||||||
self.update_central_bank(-amount, 'emission', reason)
|
self.update_central_bank(-amount, 'emission', reason)
|
||||||
logger.info(f"Money emitted: {amount} cm ({reason})")
|
logger.info(f"Money emitted: {amount} cm ({reason})")
|
||||||
|
|
|
||||||
6
main.py
6
main.py
|
|
@ -1263,12 +1263,12 @@ async def handle_deposit_cmd(message: Message):
|
||||||
await message.reply("Сумма и дни должны быть положительными.", parse_mode=None)
|
await message.reply("Сумма и дни должны быть положительными.", parse_mode=None)
|
||||||
return
|
return
|
||||||
|
|
||||||
success = await asyncio.to_thread(economy.create_deposit, message.from_user.id, amount, days)
|
success, error_msg = await asyncio.to_thread(economy.create_deposit, message.from_user.id, amount, days)
|
||||||
if success:
|
if success:
|
||||||
rate = economy.get_setting('central_bank_rate')
|
rate = economy.get_setting('central_bank_rate')
|
||||||
await message.reply(f"✅ Вклад открыт!\n💰 Сумма: {amount:.1f} см\n📅 Срок: {days} дней\n📈 Ставка: {rate}% годовых", parse_mode=None)
|
await message.reply(f"✅ Вклад открыт!\n💰 Сумма: {amount:.1f} см\n📅 Срок: {days} дней\n📈 Ставка: {rate}% годовых", parse_mode=None)
|
||||||
else:
|
else:
|
||||||
await message.reply("❌ Не удалось открыть вклад. Проверь баланс и минимальную сумму.", parse_mode=None)
|
await message.reply(f"❌ Не удалось открыть вклад. {error_msg}", parse_mode=None)
|
||||||
|
|
||||||
async def handle_loans_cmd(message: Message):
|
async def handle_loans_cmd(message: Message):
|
||||||
"""Показать кредиты"""
|
"""Показать кредиты"""
|
||||||
|
|
@ -1562,7 +1562,7 @@ async def handle_bet_cmd(message: Message):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
result = await asyncio.to_thread(place_bet, message.from_user.id, match, sport_alias, team, amount)
|
success, result = await asyncio.to_thread(place_bet, message.from_user.id, match, sport_alias, team, amount)
|
||||||
await message.reply(result, parse_mode=None)
|
await message.reply(result, parse_mode=None)
|
||||||
|
|
||||||
async def handle_mybets_cmd(message: Message):
|
async def handle_mybets_cmd(message: Message):
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,9 @@ async def create_bet(
|
||||||
raise HTTPException(status_code=400, detail="Неверный исход ставки")
|
raise HTTPException(status_code=400, detail="Неверный исход ставки")
|
||||||
|
|
||||||
# 3. Разместить ставку (синхронная функция)
|
# 3. Разместить ставку (синхронная функция)
|
||||||
result = await asyncio.to_thread(place_bet, user_id, match, req.league, team, req.amount)
|
success, result = await asyncio.to_thread(place_bet, user_id, match, req.league, team, req.amount)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=400, detail=result)
|
||||||
return {"message": result}
|
return {"message": result}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -294,7 +296,7 @@ async def play_casino_api(
|
||||||
raise HTTPException(status_code=400, detail=f"Макс ставка: {config.CASINO_MAX_BET} см")
|
raise HTTPException(status_code=400, detail=f"Макс ставка: {config.CASINO_MAX_BET} см")
|
||||||
if req.bet <= 0:
|
if req.bet <= 0:
|
||||||
raise HTTPException(status_code=400, detail="Ставка > 0")
|
raise HTTPException(status_code=400, detail="Ставка > 0")
|
||||||
if req.bet > current:
|
if round(req.bet, 2) > round(current, 2):
|
||||||
raise HTTPException(status_code=400, detail=f"У тебя {current:.1f} см")
|
raise HTTPException(status_code=400, detail=f"У тебя {current:.1f} см")
|
||||||
|
|
||||||
reels, match_count = await asyncio.to_thread(spin_slots)
|
reels, match_count = await asyncio.to_thread(spin_slots)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue