From dcf7f2b8f4cc6f5b908229f19fc81f93ddb1a945 Mon Sep 17 00:00:00 2001 From: "Gleb O. Ivaniczkij" Date: Fri, 26 Jul 2024 03:30:01 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=20=D1=87=D0=B0=D1=82-=D0=B1=D0=BE=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ai/__init__.py | 1 + ai/ai.py | 17 ++ api/__init__.py | 1 + api/api.py | 52 +++++ bot/__init__.py | 1 + bot/bot.py | 115 ++++++++++ config/__init__.py | 1 + config/config.py | 78 +++++++ database.sql | 382 +++++++++++++++++++++++++++++++++ database/__init__.py | 1 + database/database.py | 468 +++++++++++++++++++++++++++++++++++++++++ errors/__init__.py | 1 + errors/errors.py | 20 ++ fsm/__init__.py | 1 + fsm/fsm.py | 22 ++ handlers/__init__.py | 1 + handlers/handlers.py | 374 ++++++++++++++++++++++++++++++++ keyboards/__init__.py | 1 + keyboards/keyboards.py | 172 +++++++++++++++ main.py | 26 +++ messages/__init__.py | 1 + messages/messages.py | 210 ++++++++++++++++++ models/__init__.py | 6 + models/models.py | 79 +++++++ requirements.txt | 4 + static/images/1.1.png | Bin 0 -> 3808 bytes static/images/1.2.png | Bin 0 -> 4849 bytes static/images/1.3.png | Bin 0 -> 8788 bytes static/images/2.1.png | Bin 0 -> 4553 bytes static/images/2.2.png | Bin 0 -> 5006 bytes static/images/2.3.png | Bin 0 -> 3657 bytes static/images/2.4.png | Bin 0 -> 4465 bytes static/images/3.1.png | Bin 0 -> 9841 bytes static/images/3.2.png | Bin 0 -> 5816 bytes static/images/3.3.png | Bin 0 -> 5234 bytes static/images/3.4.png | Bin 0 -> 3474 bytes static/images/3.5.png | Bin 0 -> 5798 bytes static/images/4.1.png | Bin 0 -> 3448 bytes static/images/4.2.png | Bin 0 -> 2513 bytes static/images/4.3.png | Bin 0 -> 2968 bytes static/images/4.4.png | Bin 0 -> 6370 bytes static/images/4.5.png | Bin 0 -> 4187 bytes static/images/4.6.png | Bin 0 -> 5383 bytes static/images/4.7.png | Bin 0 -> 6357 bytes 44 files changed, 2035 insertions(+) create mode 100644 ai/__init__.py create mode 100644 ai/ai.py create mode 100644 api/__init__.py create mode 100644 api/api.py create mode 100644 bot/__init__.py create mode 100644 bot/bot.py create mode 100644 config/__init__.py create mode 100644 config/config.py create mode 100644 database.sql create mode 100644 database/__init__.py create mode 100644 database/database.py create mode 100644 errors/__init__.py create mode 100644 errors/errors.py create mode 100644 fsm/__init__.py create mode 100644 fsm/fsm.py create mode 100644 handlers/__init__.py create mode 100644 handlers/handlers.py create mode 100644 keyboards/__init__.py create mode 100644 keyboards/keyboards.py create mode 100644 main.py create mode 100644 messages/__init__.py create mode 100644 messages/messages.py create mode 100644 models/__init__.py create mode 100644 models/models.py create mode 100644 requirements.txt create mode 100644 static/images/1.1.png create mode 100644 static/images/1.2.png create mode 100644 static/images/1.3.png create mode 100644 static/images/2.1.png create mode 100644 static/images/2.2.png create mode 100644 static/images/2.3.png create mode 100644 static/images/2.4.png create mode 100644 static/images/3.1.png create mode 100644 static/images/3.2.png create mode 100644 static/images/3.3.png create mode 100644 static/images/3.4.png create mode 100644 static/images/3.5.png create mode 100644 static/images/4.1.png create mode 100644 static/images/4.2.png create mode 100644 static/images/4.3.png create mode 100644 static/images/4.4.png create mode 100644 static/images/4.5.png create mode 100644 static/images/4.6.png create mode 100644 static/images/4.7.png diff --git a/ai/__init__.py b/ai/__init__.py new file mode 100644 index 0000000..80dae48 --- /dev/null +++ b/ai/__init__.py @@ -0,0 +1 @@ +from .ai import update_statistics diff --git a/ai/ai.py b/ai/ai.py new file mode 100644 index 0000000..90d4be8 --- /dev/null +++ b/ai/ai.py @@ -0,0 +1,17 @@ +import database +import logging +import traceback +import time + +import config + + +def update_statistics(): + if not config.AI.enabled: + return + while True: + try: + database.update_statistics(config.AI.k) + except Exception: + logging.error('\n\t%s' % '\n\t'.join(traceback.format_exc().split('\n')).rstrip()) + time.sleep(config.AI.update_frequency) diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 0000000..34ac65b --- /dev/null +++ b/api/__init__.py @@ -0,0 +1 @@ +from .api import create_issue, read_issue diff --git a/api/api.py b/api/api.py new file mode 100644 index 0000000..6a1bb6c --- /dev/null +++ b/api/api.py @@ -0,0 +1,52 @@ +import config +from jira import JIRA, Issue +from models import date, Subcategory, TimeRange + + +jira = JIRA( + basic_auth=( + config.Jira.username, + config.Jira.token, + ), + server=config.Jira.server, +) + + +time_ranges = { + 1: ('T09:00:00.000+0300', 'T12:00:00.000+0300'), + 2: ('T12:00:00.000+0300', 'T15:00:00.000+0300'), + 3: ('T15:00:00.000+0300', 'T18:00:00.000+0300'), +} + + +async def create_issue( + subcategory: Subcategory, + _date: date, + time_range: TimeRange, + email_address: str, + phone_number: str, + comment: str, + firstname: str, +) -> Issue: + return jira.create_issue( + project='SFX', + summary='Задача от %s' % firstname, + issuetype={ + 'name': 'Telegram API', + }, + customfield_10033={ + 'value': subcategory.category.name, + 'child': { + 'value': subcategory.name, + }, + }, + customfield_10035='%s%s' % (str(_date), time_ranges[time_range.id][0]), + customfield_10036='%s%s' % (str(_date), time_ranges[time_range.id][1]), + customfield_10037=email_address, + customfield_10038=phone_number, + customfield_10039=comment, + ) + + +async def read_issue(issue_id: str): + return jira.issue(issue_id) diff --git a/bot/__init__.py b/bot/__init__.py new file mode 100644 index 0000000..e81fe17 --- /dev/null +++ b/bot/__init__.py @@ -0,0 +1 @@ +from .bot import dispatcher, storage diff --git a/bot/bot.py b/bot/bot.py new file mode 100644 index 0000000..ccdd6b3 --- /dev/null +++ b/bot/bot.py @@ -0,0 +1,115 @@ +from aiogram import Bot, Dispatcher +from aiogram.contrib.fsm_storage.memory import MemoryStorage + +import config +from fsm import FSM +import handlers +import messages + + +bot = Bot( + token=config.Bot.token, +) + +storage = MemoryStorage() + +dispatcher = Dispatcher( + bot=bot, + storage=storage, +) + +dispatcher.register_message_handler( + callback=messages.start, + commands=['start'], + state='*', +) + +dispatcher.register_message_handler( + callback=handlers.start, + state=FSM.start, +) + +dispatcher.register_message_handler( + callback=handlers.select_category, + state=FSM.select_category, +) + +dispatcher.register_message_handler( + callback=handlers.select_subcategory, + state=FSM.select_subcategory, +) + +dispatcher.register_message_handler( + callback=handlers.select_date, + state=FSM.select_date, +) + +dispatcher.register_message_handler( + callback=handlers.select_time_range, + state=FSM.select_time_range, +) + +dispatcher.register_message_handler( + callback=handlers.input_email_address, + state=FSM.input_email_address, +) + +dispatcher.register_message_handler( + callback=handlers.input_phone_number, + state=FSM.input_phone_number, +) + +dispatcher.register_message_handler( + callback=handlers.input_comment, + state=FSM.input_comment, +) + +dispatcher.register_message_handler( + callback=handlers.confirm_order, + state=FSM.confirm_order, +) + +dispatcher.register_message_handler( + callback=handlers.success_sign_up, + state=FSM.success_sign_up, +) + +dispatcher.register_message_handler( + callback=handlers.select_order, + state=FSM.select_order, +) + +dispatcher.register_message_handler( + callback=handlers.no_orders, + state=FSM.no_orders, +) + +dispatcher.register_message_handler( + callback=handlers.select_operation, + state=FSM.select_operation, +) + +dispatcher.register_message_handler( + callback=handlers.reschedule_order_select_date, + state=FSM.reschedule_order_select_date, +) + +dispatcher.register_message_handler( + callback=handlers.reschedule_order_select_time_range, + state=FSM.reschedule_order_select_time_range, +) + +dispatcher.register_message_handler( + callback=handlers.success_reschedule_order, + state=FSM.success_reschedule_order, +) + +dispatcher.register_message_handler( + callback=handlers.cancel_order, + state=FSM.cancel_order, +) + +dispatcher.register_message_handler( + callback=handlers.success_cancel_order, + state=FSM.success_cancel_order, +) diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..0a56c31 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +from .config import Bot, Database, AI, Jira diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..2afab59 --- /dev/null +++ b/config/config.py @@ -0,0 +1,78 @@ +from configparser import RawConfigParser + + +config = RawConfigParser() +config.read( + filenames='./config.ini', +) + + +class Bot: + token = config.get( + section='Bot', + option='token', + ) + + +class Database: + dbname = config.get( + section='Database', + option='dbname', + ) + + user = config.get( + section='Database', + option='user', + ) + + password = config.get( + section='Database', + option='password', + ) + + host = config.get( + section='Database', + option='host', + fallback='localhost', + ) + + port = config.getint( + section='Database', + option='port', + fallback=5432, + ) + + +class AI: + enabled = config.getboolean( + section='AI', + option='enabled', + fallback=True, + ) + + k = config.getint( + section='AI', + option='k', + fallback=3, + ) + + update_frequency = config.getint( + section='AI', + option='update_frequency', + fallback=86400, + ) + + +class Jira: + username = config.get( + section='Jira', + option='username', + ) + token = config.get( + section='Jira', + option='token', + ) + server = config.get( + section='Jira', + option='server', + ) diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..b32d8c8 --- /dev/null +++ b/database.sql @@ -0,0 +1,382 @@ +create table categories ( + id smallserial not null, + name character varying (64) not null, + description text not null, + primary key (id), + unique (name) +); + +insert into categories (name, description) values +('Прикладное программное обеспечение', '[Описание категории]'), +('Системное программное обеспечение', '[Описание категории]'), +('Системы обеспечения IT-безопасности', '[Описание категории]'), +('Специализированное программное обеспечение', '[Описание категории]'); + +create table subcategories ( + category_id smallint not null, + id smallint not null, + name character varying (64) not null, + description text not null, + foreign key (category_id) references categories on delete cascade, + primary key (category_id, id), + unique (category_id, name) +); + +create function sfx_subcategories_id_seq() returns trigger as $$ + begin + if new.id is null then + new.id := (select + coalesce(max(subcategories.id) + 1, 1) + from + subcategories + where + subcategories.category_id = new.category_id); + end if; + return new; + end; +$$ language plpgsql; + +create trigger subcategories_id_seq before insert on subcategories + for each row execute procedure sfx_subcategories_id_seq(); + +insert into subcategories (category_id, name, description) values +(1, 'Программы 1С', '[Описание подкатегории]'), +(1, 'Программы ЭОС', '[Описание подкатегории]'), +(1, 'Собственные разработки на платформе 1С', '[Описание подкатегории]'), +(2, 'Продукция Astra Linux', '[Описание подкатегории]'), +(2, 'Продукция Alt Linux (Базальт-СПО)', '[Описание подкатегории]'), +(2, 'ПО для работы с текстом', '[Описание подкатегории]'), +(2, 'МойОфис', '[Описание подкатегории]'), +(3, 'Системы обеспечения сохранности данных', '[Описание подкатегории]'), +(3, 'Антивирусное ПО', '[Описание подкатегории]'), +(3, 'Системы защиты корпоративной информации', '[Описание подкатегории]'), +(3, 'Программный комплекс «Стахановец»', '[Описание подкатегории]'), +(3, 'Решение StaffCop Enterprise', '[Описание подкатегории]'), +(4, 'Графические редакторы Movavi', '[Описание подкатегории]'), +(4, 'Сметные программы', '[Описание подкатегории]'), +(4, 'Библиотеки нормативов и стандартов', '[Описание подкатегории]'), +(4, 'САПР', '[Описание подкатегории]'), +(4, 'Решения для совместной работы TrueConf', '[Описание подкатегории]'), +(4, 'Polys - система безопасных онлайн-голосований', '[Описание подкатегории]'), +(4, 'VISOCALL IP Телекоммуникация в медицине', '[Описание подкатегории]'); + +create table executors ( + id bigserial not null, + name character varying (64) not null, + primary key (id), + unique (name) +); + +insert into executors (name) values +('Богатырёва Ю. И.'), +('Ванькова В. С.'), +('Даниленко С. В.'), +('Екатериничев А. Л.'), +('Клепиков А. К.'), +('Мартынюк Ю. М.'), +('Надеждин Е. Н.'), +('Привалов А. Н.'), +('Родионова О. В.'); + +create table executor_specialties ( + executor_id bigint not null, + category_id smallint not null, + subcategory_id smallint not null, + foreign key (executor_id) references executors on delete cascade, + foreign key (category_id, subcategory_id) references subcategories on delete cascade, + primary key (executor_id, category_id, subcategory_id) +); + +insert into executor_specialties (executor_id, category_id, subcategory_id) values +(1, 1, 1), +(2, 1, 1), +(4, 1, 1), +(5, 1, 1), +(6, 1, 1), +(7, 1, 1), +(8, 1, 1), +(9, 1, 1), +(4, 1, 2), +(5, 1, 2), +(7, 1, 2), +(8, 1, 2), +(1, 1, 3), +(2, 1, 3), +(3, 1, 3), +(5, 1, 3), +(6, 1, 3), +(7, 1, 3), +(8, 1, 3), +(4, 2, 1), +(5, 2, 1), +(6, 2, 1), +(7, 2, 1), +(8, 2, 1), +(9, 2, 1), +(2, 2, 2), +(5, 2, 2), +(6, 2, 2), +(7, 2, 2), +(8, 2, 2), +(9, 2, 2), +(2, 2, 3), +(4, 2, 3), +(8, 2, 3), +(4, 2, 4), +(5, 2, 4), +(8, 2, 4), +(9, 2, 4), +(2, 3, 1), +(3, 3, 1), +(6, 3, 1), +(7, 3, 1), +(1, 3, 2), +(4, 3, 2), +(5, 3, 2), +(6, 3, 2), +(9, 3, 2), +(1, 3, 3), +(6, 3, 3), +(9, 3, 3), +(3, 3, 4), +(8, 3, 4), +(1, 3, 5), +(3, 3, 5), +(5, 3, 5), +(6, 3, 5), +(7, 3, 5), +(9, 3, 5), +(5, 4, 1), +(6, 4, 1), +(7, 4, 2), +(9, 4, 4), +(3, 4, 6), +(4, 4, 6); + +create table time_ranges ( + id smallserial not null, + start_time time not null, + end_time time not null, + primary key (id) +); + +insert into time_ranges (start_time, end_time) values +('9:00'::time, '12:00'::time), +('12:00'::time, '15:00'::time), +('15:00'::time, '18:00'::time); + +create table orders ( + id bigserial not null, + category_id smallint not null, + subcategory_id smallint not null, + date date not null, + time_range_id smallint not null, + executor_id bigint not null, + telegram_id bigint not null, + email_address character varying (256) not null, + phone_number character varying (16) not null, + comment character varying (1024) not null, + start_time timestamp, + end_time timestamp, + foreign key (category_id, subcategory_id) references subcategories on delete cascade, + foreign key (time_range_id) references time_ranges on delete cascade, + foreign key (executor_id) references executors on delete cascade, + primary key (id) +); + +create table issues ( + id bigint not null, + key character varying (16) not null, + status character varying (16) not null, + telegram_id bigint not null, + primary key (id), + unique (key) +); + +create table statistics ( + category_id smallint not null, + subcategory_id smallint not null, + execution_time interval not null, + foreign key (category_id, subcategory_id) references subcategories on delete cascade, + primary key (category_id, subcategory_id) +); + +insert into statistics (category_id, subcategory_id, execution_time) values +(1, 1, interval '15 minutes'), +(1, 2, interval '30 minutes'), +(1, 3, interval '15 minutes'), +(2, 1, interval '1 hour'), +(2, 2, interval '2 hours'), +(2, 3, interval '1 hour'), +(2, 4, interval '2 hours'), +(3, 1, interval '15 minutes'), +(3, 2, interval '30 minutes'), +(3, 3, interval '15 minutes'), +(3, 4, interval '30 minutes'), +(3, 5, interval '15 minutes'), +(4, 1, interval '1 hour'), +(4, 2, interval '2 hours'), +(4, 3, interval '1 hour'), +(4, 4, interval '2 hours'), +(4, 5, interval '1 hour'), +(4, 6, interval '2 hours'), +(4, 7, interval '1 hour'); + +create or replace function sfx_update_statistics( + k integer +) returns void as $$ + declare subcategory subcategories; + declare _order record; + begin + for subcategory in ( + select + subcategories.category_id, + subcategories.id + from + subcategories + ) loop + for _order in ( + select + count(t1) as count, + avg(t1.end_time - t1.start_time) as avg + from + ( + select + orders.start_time, + orders.end_time + from + orders + where + orders.category_id = subcategory.category_id and + orders.subcategory_id = subcategory.id and + orders.start_time is not null and + orders.end_time is not null + order by + orders.start_time desc + limit + k + ) t1 + ) loop + if _order.count <> k then + continue; + end if; + update + statistics + set + execution_time = _order.avg + where + statistics.category_id = subcategory.category_id and + statistics.subcategory_id = subcategory.id; + end loop; + end loop; + end; +$$ language plpgsql; + +create or replace function sfx_read_free_to_order( + _category_id smallint, + _subcategory_id smallint +) returns table ( + date date, + time_range_id smallint, + executor_id bigint, + busy_interval interval +) as $$ + declare avg_interval interval := ( + select + statistics.execution_time + from + statistics + where + statistics.category_id = _category_id and + statistics.subcategory_id = _subcategory_id + ); + declare max_interval interval := ( + select + max(time_ranges.end_time - time_ranges.start_time) + from + time_ranges + ); + declare executor executors; + declare time_range time_ranges; + declare _date date := now()::date; + declare _busy_interval interval; + begin + if avg_interval > max_interval then + raise '0x00000005'; + end if; + create temporary table tmp ( + date date not null, + time_range_id smallint not null, + executor_id bigint not null, + busy_interval interval not null + ) on commit drop; + while ( + select + coalesce(count(t1.*), 0) < 6 + from + (select distinct + tmp.date + from + tmp) t1 + ) loop + for executor in ( + select + executors.id + from + executor_specialties + left join executors on + executor_specialties.executor_id = executors.id + where + executor_specialties.category_id = _category_id and + executor_specialties.subcategory_id = _subcategory_id + ) loop + for time_range in ( + select + time_ranges.id, + time_ranges.start_time, + time_ranges.end_time + from + time_ranges + ) loop + if _date = now()::date and time_range.start_time < now()::time then + continue; + end if; + _busy_interval := ( + select + coalesce(sum(statistics.execution_time), '0s'::interval) + from + orders + left join statistics on + orders.category_id = statistics.category_id and + orders.subcategory_id = statistics.subcategory_id + where + orders.date = _date and + orders.time_range_id = time_range.id and + orders.executor_id = executor.id + ); + if time_range.end_time - time_range.start_time - _busy_interval > avg_interval then + insert into tmp ( + date, + time_range_id, + executor_id, + busy_interval + ) + values ( + _date, + time_range.id, + executor.id, + _busy_interval + ); + end if; + end loop; + end loop; + _date = _date + '1 day'::interval; + end loop; + return query ( + select + tmp.* + from + tmp + ); + end; +$$ language plpgsql; diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..b0d5efc --- /dev/null +++ b/database/__init__.py @@ -0,0 +1 @@ +from .database import read_categories, read_subcategories, read_free_to_order, create_order, read_orders, update_order, delete_order, update_statistics, create_issue, read_issues, delete_issue diff --git a/database/database.py b/database/database.py new file mode 100644 index 0000000..dd5c156 --- /dev/null +++ b/database/database.py @@ -0,0 +1,468 @@ +import config +from models import date, Category, Subcategory, Executor, TimeRange, FreeToOrder, Order, Issue +from psycopg import AsyncConnection, Connection + + +conninfo = 'dbname=%(dbname)s user=%(user)s password=%(password)s host=%(host)s port=%(port)d' % { + 'dbname': config.Database.dbname, + 'user': config.Database.user, + 'password': config.Database.password, + 'host': config.Database.host, + 'port': config.Database.port, +} + + +async def read_categories() -> tuple[Category]: + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + select distinct + categories.id, + categories.name, + categories.description + from + categories + right join executor_specialties on + categories.id = executor_specialties.category_id + order by + categories.id; + ''' + await cursor.execute(sql) + records = await cursor.fetchall() + return tuple( + Category( + id=category_id, + name=name, + description=description, + ) + for + category_id, + name, + description, + in records + ) + + +async def read_subcategories(category: Category) -> tuple[Subcategory]: + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + select distinct + categories.id, + categories.name, + categories.description, + subcategories.id, + subcategories.name, + subcategories.description + from + subcategories + left join categories on + subcategories.category_id = categories.id + right join executor_specialties on + categories.id = executor_specialties.category_id and + subcategories.id = executor_specialties.subcategory_id + where + categories.id = %(category_id)s + order by + subcategories.id; + ''' + await cursor.execute( + sql, + { + 'category_id': category.id, + }, + ) + records = await cursor.fetchall() + return tuple( + Subcategory( + category=Category( + id=category_id, + name=category_name, + description=category_description, + ), + id=subcategory_id, + name=subcategory_name, + description=subcategory_description, + ) + for + category_id, + category_name, + category_description, + subcategory_id, + subcategory_name, + subcategory_description, + in records + ) + + +async def read_free_to_order(subcategory: Subcategory) -> tuple[FreeToOrder]: + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + select + sfx_read_free_to_order.date, + time_ranges.id, + time_ranges.start_time, + time_ranges.end_time, + executors.id, + executors.name, + sfx_read_free_to_order.busy_interval + from + sfx_read_free_to_order( + %(category_id)s::smallint, + %(subcategory_id)s::smallint + ) + left join time_ranges on + sfx_read_free_to_order.time_range_id = time_ranges.id + left join executors on + sfx_read_free_to_order.executor_id = executors.id; + ''' + await cursor.execute( + sql, + { + 'category_id': subcategory.category.id, + 'subcategory_id': subcategory.id + }, + ) + records = await cursor.fetchall() + return tuple( + FreeToOrder( + date=free_to_order_date, + time_range=TimeRange( + id=time_range_id, + start_time=time_range_start_time, + end_time=time_range_end_time, + ), + executor=Executor( + id=executor_id, + name=executor_name, + ), + busy_interval=free_to_order_busy_interval, + ) + for + free_to_order_date, + time_range_id, + time_range_start_time, + time_range_end_time, + executor_id, + executor_name, + free_to_order_busy_interval, + in records + ) + + +async def create_order( + subcategory: Subcategory, + date: date, + time_range: TimeRange, + executor: Executor, + telegram_id: int, + email_address: str, + phone_number: str, + comment: str, +) -> int: + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + insert into orders ( + category_id, + subcategory_id, + date, + time_range_id, + executor_id, + telegram_id, + email_address, + phone_number, + comment + ) values ( + %(category_id)s, + %(subcategory_id)s, + %(date)s, + %(time_range_id)s, + %(executor_id)s, + %(telegram_id)s, + %(email_address)s, + %(phone_number)s, + %(comment)s + ) returning + orders.id; + ''' + await cursor.execute( + sql, + { + 'category_id': subcategory.category.id, + 'subcategory_id': subcategory.id, + 'date': date, + 'time_range_id': time_range.id, + 'executor_id': executor.id, + 'telegram_id': telegram_id, + 'email_address': email_address, + 'phone_number': phone_number, + 'comment': comment, + }, + ) + record = await cursor.fetchone() + return record[0] + + +async def read_orders( + telegram_id: int, +) -> tuple[Order]: + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + select + orders.id, + categories.id, + categories.name, + categories.description, + subcategories.id, + subcategories.name, + subcategories.description, + orders.date, + time_ranges.id, + time_ranges.start_time, + time_ranges.end_time, + executors.id, + executors.name, + orders.telegram_id, + orders.email_address, + orders.phone_number, + orders.comment, + orders.start_time, + orders.end_time + from + orders + left join categories on + orders.category_id = categories.id + left join subcategories on + orders.category_id = subcategories.category_id and + orders.subcategory_id = subcategories.id + left join time_ranges on + orders.time_range_id = time_ranges.id + left join executors on + orders.executor_id = executors.id + where + orders.telegram_id = %(telegram_id)s and + orders.start_time is null and + orders.end_time is null + order by + orders.id; + ''' + await cursor.execute( + sql, + { + 'telegram_id': telegram_id, + }, + ) + records = await cursor.fetchall() + return tuple( + Order( + id=order_id, + subcategory=Subcategory( + category=Category( + id=category_id, + name=category_name, + description=category_description, + ), + id=subcategory_id, + name=subcategory_name, + description=subcategory_description, + ), + date=order_date, + time_range=TimeRange( + id=time_range_id, + start_time=time_range_start_time, + end_time=time_range_end_time, + ), + executor=Executor( + id=executor_id, + name=executor_name, + ), + telegram_id=order_telegram_id, + email_address=order_email_address, + phone_number=order_phone_number, + comment=order_comment, + start_time=order_start_time, + end_time=order_end_time, + ) + for + order_id, + category_id, + category_name, + category_description, + subcategory_id, + subcategory_name, + subcategory_description, + order_date, + time_range_id, + time_range_start_time, + time_range_end_time, + executor_id, + executor_name, + order_telegram_id, + order_email_address, + order_phone_number, + order_comment, + order_start_time, + order_end_time, + in records + ) + + +async def update_order( + order_id: int, + date: date, + time_range: TimeRange, + executor: Executor, +): + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + update + orders + set + date = %(date)s, + time_range_id = %(time_range_id)s, + executor_id = %(executor_id)s + where + orders.id = %(order_id)s; + ''' + await cursor.execute( + sql, + { + 'order_id': order_id, + 'date': date, + 'time_range_id': time_range.id, + 'executor_id': executor.id, + }, + ) + + +async def delete_order( + order_id: int, +): + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + delete from + orders + where + orders.id = %(order_id)s; + ''' + await cursor.execute( + sql, + { + 'order_id': order_id, + }, + ) + + +def update_statistics( + k: int, +): + with Connection.connect(conninfo) as connection: + with connection.cursor() as cursor: + sql = ''' + select + sfx_update_statistics( + %(k)s + ); + ''' + cursor.execute( + sql, + { + 'k': k, + }, + ) + + +async def create_issue( + issue: Issue, +): + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + insert into issues ( + id, + key, + status, + telegram_id + ) values ( + %(issue_id)s, + %(key)s, + %(status)s, + %(telegram_id)s + ); + ''' + await cursor.execute( + sql, + { + 'issue_id': issue.id, + 'key': issue.key, + 'status': issue.status, + 'telegram_id': issue.telegram_id, + }, + ) + + +async def read_issues( + telegram_id: int, +) -> tuple[Issue]: + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + select + issues.id, + issues.key, + issues.status, + issues.telegram_id + from + issues + where + issues.telegram_id = %(telegram_id)s and + issues.status != 'done' + order by + issues.id; + ''' + await cursor.execute( + sql, + { + 'telegram_id': telegram_id, + }, + ) + records = await cursor.fetchall() + return tuple( + Issue( + id=issue_id, + key=key, + status=status, + telegram_id=telegram_id, + ) + for + issue_id, + key, + status, + telegram_id, + in + records + ) + + +async def delete_issue( + issue: Issue, +): + async with await AsyncConnection.connect(conninfo) as connection: + async with connection.cursor() as cursor: + sql = ''' + delete from + issues + where + issues.id = %(issue_id)s + ''' + await cursor.execute( + sql, + { + 'issue_id': issue.id, + }, + ) diff --git a/errors/__init__.py b/errors/__init__.py new file mode 100644 index 0000000..b159600 --- /dev/null +++ b/errors/__init__.py @@ -0,0 +1 @@ +from .errors import Error diff --git a/errors/errors.py b/errors/errors.py new file mode 100644 index 0000000..579ea37 --- /dev/null +++ b/errors/errors.py @@ -0,0 +1,20 @@ +class Error(str): + errors = { + 0x00000000: 'Неизвестная ошибка.', + 0x00000001: 'Выберете из предложенного.', + 0x00000002: 'Текст слишком длинный.', + 0x00000003: 'Введите корректный адрес электронной почты.', + 0x00000004: 'Введите корректный номер телефона.', + 0x00000005: 'К сожалению, бот не способен обрабатывать заказы этой подкатегории.\n\nСвяжитесь с нами любым удобным для вас способом:\n+7 (4872) 70-02-70 — телефон в Туле;\n8-800-775-15-40 — по России бесплатно;\nsfx@sfx-tula.ru — электронная почта.', + } + + def __new__(cls, code): + self = str.__new__(Error) + self.code = code + return self + + def __str__(self): + return 'Ошибка 0x%.8X!\n%s' % ( + self.code, + self.errors[self.code], + ) diff --git a/fsm/__init__.py b/fsm/__init__.py new file mode 100644 index 0000000..cf25615 --- /dev/null +++ b/fsm/__init__.py @@ -0,0 +1 @@ +from .fsm import FSM diff --git a/fsm/fsm.py b/fsm/fsm.py new file mode 100644 index 0000000..f417d62 --- /dev/null +++ b/fsm/fsm.py @@ -0,0 +1,22 @@ +from aiogram.dispatcher.filters.state import StatesGroup, State + + +class FSM(StatesGroup): + start = State() + select_category = State() + select_subcategory = State() + select_date = State() + select_time_range = State() + input_email_address = State() + input_phone_number = State() + input_comment = State() + confirm_order = State() + success_sign_up = State() + select_order = State() + no_orders = State() + select_operation = State() + reschedule_order_select_date = State() + reschedule_order_select_time_range = State() + success_reschedule_order = State() + cancel_order = State() + success_cancel_order = State() diff --git a/handlers/__init__.py b/handlers/__init__.py new file mode 100644 index 0000000..0e0b0cf --- /dev/null +++ b/handlers/__init__.py @@ -0,0 +1 @@ +from .handlers import start, select_category, select_subcategory, select_date, select_time_range, input_email_address, input_phone_number, input_comment, confirm_order, success_sign_up, select_order, no_orders, select_operation, reschedule_order_select_date, reschedule_order_select_time_range, success_reschedule_order, cancel_order, success_cancel_order diff --git a/handlers/handlers.py b/handlers/handlers.py new file mode 100644 index 0000000..f731821 --- /dev/null +++ b/handlers/handlers.py @@ -0,0 +1,374 @@ +import api +import database +import models +from errors import Error +import logging +import messages +from models import Message, FSMContext +import psycopg +import re + + +async def start(message: Message, state: FSMContext): + if message.text == 'Записаться': + return await messages.select_category(message, state) + if message.text == 'Мои обращения': + return await messages.select_order(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def select_category(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.start(message, state) + try: + category = tuple( + category + for category in await database.read_categories() + if category.name == message.text + )[0] + await state.update_data({ + 'category': category, + }) + await messages.select_subcategory(message, state) + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def select_subcategory(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_category(message, state) + try: + data = await state.get_data() + subcategory = tuple( + subcategory + for subcategory in await database.read_subcategories(data['category']) + if subcategory.name == message.text + )[0] + try: + free_to_order = await database.read_free_to_order(subcategory) + await state.update_data({ + 'subcategory': subcategory, + 'free_to_order': free_to_order, + }) + await messages.select_date(message, state) + except psycopg.errors.RaiseException as error: + if error.diag.message_primary == '0x00000005': + logging.error('\n\tКатегория: %s\n\tПодкатегория: %s\n\n\tСредняя длительность выполнения заказа указанной подкатегории (таблица statistics) дольше, чем все временные промежутки (таблица time_ranges).' % ( + subcategory.category.name, + subcategory.name, + )) + return await message.answer( + text=Error(0x00000005), + parse_mode='HTML', + ) + raise + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def select_date(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_subcategory(message, state) + try: + data = await state.get_data() + free_to_order = tuple( + item + for item in data['free_to_order'] + if item.date.strftime('%d.%m') == message.text + )[0] + await state.update_data({ + 'date': free_to_order.date, + }) + await messages.select_time_range(message, state) + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def select_time_range(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_date(message, state) + try: + data = await state.get_data() + free_to_order = tuple( + item + for item in data['free_to_order'] + if '%s - %s' % ( + item.time_range.start_time.strftime('%H:%M'), + item.time_range.end_time.strftime('%H:%M'), + ) == message.text + )[0] + await state.update_data({ + 'time_range': free_to_order.time_range, + }) + await messages.input_email_address(message, state) + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def input_email_address(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_time_range(message, state) + match = re.match(models.email_address, message.text.strip()) + if match is None: + return await message.answer( + text=Error(0x00000003), + parse_mode='HTML', + ) + await state.update_data({ + 'email_address': match.string, + }) + await messages.input_phone_number(message, state) + + +async def input_phone_number(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.input_email_address(message, state) + phone_number = re.sub(models.phone_number, '', message.text) + if len(phone_number) not in range(8, 16): + return await message.answer( + text=Error(0x00000004), + parse_mode='HTML', + ) + await state.update_data({ + 'phone_number': '+%s' % phone_number, + }) + await messages.input_comment(message, state) + + +async def input_comment(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.input_phone_number(message, state) + comment = message.text + if len(comment) > models.max_comment_length: + return await message.answer( + text=Error(0x00000002), + parse_mode='HTML', + ) + await state.update_data({ + 'comment': comment, + }) + await messages.confirm_order(message, state) + + +async def confirm_order(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.input_comment(message, state) + if message.text == 'Подтвердить': + data = await state.get_data() + # executor = sorted( + # tuple( + # item + # for item in data['free_to_order'] + # if item.date == data['date'] and item.time_range == data['time_range'] + # ), + # key=lambda x: x.busy_interval, + # )[0].executor + # order_id = await database.create_order( + # subcategory=data['subcategory'], + # date=data['date'], + # time_range=data['time_range'], + # executor=executor, + # telegram_id=message.from_user.id, + # email_address=data['email_address'], + # phone_number=data['phone_number'], + # comment=data['comment'], + # ) + # orders = await database.read_orders(message.from_user.id) + issue = await api.create_issue( + subcategory=data['subcategory'], + _date=data['date'], + time_range=data['time_range'], + email_address=data['email_address'], + phone_number=data['phone_number'], + comment=data['comment'], + firstname=message.from_user.first_name, + ) + await state.update_data({ + # 'executor': executor, + 'issue_key': issue.key, + }) + await database.create_issue( + issue=models.Issue( + id=issue.id, + key=issue.key, + status='new', + telegram_id=message.from_user.id, + ), + ) + return await messages.success_sign_up(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def success_sign_up(message: Message, state: FSMContext): + if message.text == 'Главное меню': + return await messages.start(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def select_order(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.start(message, state) + try: + issue = tuple( + issue + for issue in await database.read_issues(message.from_user.id) + if issue.key == message.text + )[0] + await state.update_data({ + 'issue': issue, + }) + await messages.select_operation(message, state) + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def no_orders(message: Message, state: FSMContext): + if message.text == 'Главное меню': + return await messages.start(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def select_operation(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_order(message, state) + if message.text == 'Перенести': + data = await state.get_data() + free_to_order = await database.read_free_to_order(data['order'].subcategory) + await state.update_data({ + 'free_to_order': free_to_order, + }) + return await messages.reschedule_order_select_date(message, state) + if message.text == 'Отменить': + return await messages.cancel_order(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def reschedule_order_select_date(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_operation(message, state) + try: + data = await state.get_data() + free_to_order = tuple( + item + for item in data['free_to_order'] + if item.date.strftime('%d.%m') == message.text + )[0] + await state.update_data({ + 'date': free_to_order.date, + }) + await messages.reschedule_order_select_time_range(message, state) + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def reschedule_order_select_time_range(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.reschedule_order_select_date(message, state) + try: + data = await state.get_data() + free_to_order = tuple( + item + for item in data['free_to_order'] + if '%s - %s' % ( + item.time_range.start_time.strftime('%H:%M'), + item.time_range.end_time.strftime('%H:%M'), + ) == message.text + )[0] + executor = sorted( + tuple( + item + for item in data['free_to_order'] + if item.date == data['date'] and item.time_range == free_to_order.time_range + ), + key=lambda x: x.busy_interval, + )[0].executor + await state.update_data({ + 'executor': executor, + }) + await database.update_order( + order_id=data['order'].id, + date=data['date'], + time_range=free_to_order.time_range, + executor=executor, + ) + await messages.success_reschedule_order(message, state) + except IndexError: + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def success_reschedule_order(message: Message, state: FSMContext): + if message.text == 'Мои обращения': + await state.reset_data() + return await messages.select_order(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def cancel_order(message: Message, state: FSMContext): + if message.text == 'Назад': + return await messages.select_operation(message, state) + if message.text == 'Отменить заказ': + data = await state.get_data() + issue = await api.read_issue( + issue_id=data['issue'].id, + ) + issue.delete() + await database.delete_issue( + issue=issue, + ) + # await database.delete_order( + # order_id=data['issue'].id, + # ) + return await messages.success_cancel_order(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) + + +async def success_cancel_order(message: Message, state: FSMContext): + if message.text == 'Мои обращения': + await state.reset_data() + return await messages.select_order(message, state) + await message.answer( + text=Error(0x00000001), + parse_mode='HTML', + ) diff --git a/keyboards/__init__.py b/keyboards/__init__.py new file mode 100644 index 0000000..f749bc9 --- /dev/null +++ b/keyboards/__init__.py @@ -0,0 +1 @@ +from .keyboards import start, categories, subcategories, dates, time_ranges, back, order_confirmation, main_menu, orders, operations, cancel_order, my_orders, issues diff --git a/keyboards/keyboards.py b/keyboards/keyboards.py new file mode 100644 index 0000000..cb75c9e --- /dev/null +++ b/keyboards/keyboards.py @@ -0,0 +1,172 @@ +import database +from models import date, ReplyKeyboardMarkup, KeyboardButton, Category, FreeToOrder, Order, Issue + + +def start(): + return ReplyKeyboardMarkup([ + [ + KeyboardButton('Записаться'), + ], + [ + KeyboardButton('Мои обращения'), + ], + ]) + + +async def categories(): + keyboard = ReplyKeyboardMarkup() + for category in await database.read_categories(): + keyboard.add( + KeyboardButton(category.name), + ) + keyboard.add( + KeyboardButton('Назад'), + ) + return keyboard + + +async def subcategories(category: Category): + keyboard = ReplyKeyboardMarkup() + for subcategory in await database.read_subcategories(category): + keyboard.add( + KeyboardButton(subcategory.name), + ) + keyboard.add( + KeyboardButton('Назад'), + ) + return keyboard + + +def dates(free_to_order: tuple[FreeToOrder]): + _dates = tuple( + sorted( + set( + item.date + for item in free_to_order + ) + ) + ) + keyboard = ReplyKeyboardMarkup() + for _date in _dates: + keyboard.insert(_date.strftime('%d.%m')) + keyboard.add( + KeyboardButton('Назад'), + ) + return keyboard + + +def time_ranges(free_to_order: tuple[FreeToOrder], _date: date): + _time_ranges = tuple( + sorted( + set( + item.time_range + for item in free_to_order + if item.date == _date + ), + key=lambda x: x.id, + ) + ) + keyboard = ReplyKeyboardMarkup() + for time_range in _time_ranges: + keyboard.insert('%s - %s' % ( + time_range.start_time.strftime('%H:%M'), + time_range.end_time.strftime('%H:%M'), + )) + keyboard.add( + KeyboardButton('Назад'), + ) + return keyboard + + +def back(): + return ReplyKeyboardMarkup( + keyboard=[ + [ + KeyboardButton('Назад'), + ], + ], + ) + + +def order_confirmation(): + return ReplyKeyboardMarkup( + keyboard=[ + [ + KeyboardButton('Подтвердить'), + ], + [ + KeyboardButton('Назад'), + ], + ], + ) + + +def main_menu(): + return ReplyKeyboardMarkup( + keyboard=[ + [ + KeyboardButton('Главное меню'), + ], + ], + ) + + +def orders(_orders: tuple[Order]): + keyboard = ReplyKeyboardMarkup() + for order in _orders: + keyboard.insert( + KeyboardButton('№%d' % order.id), + ) + keyboard.add( + KeyboardButton('Назад'), + ) + return keyboard + + +def operations(): + return ReplyKeyboardMarkup( + keyboard=[ + [ + # KeyboardButton('Перенести'), + KeyboardButton('Отменить'), + ], + [ + KeyboardButton('Назад'), + ], + ], + ) + + +def cancel_order(): + return ReplyKeyboardMarkup( + keyboard=[ + [ + KeyboardButton('Отменить заказ'), + ], + [ + KeyboardButton('Назад'), + ], + ], + ) + + +def my_orders(): + return ReplyKeyboardMarkup( + keyboard=[ + [ + KeyboardButton('Мои обращения'), + ], + ], + ) + + +def issues(_issues: tuple[Issue]): + keyboard = ReplyKeyboardMarkup() + for issue in _issues: + keyboard.insert( + KeyboardButton('%s' % issue.key), + ) + keyboard.add( + KeyboardButton('Назад'), + ) + return keyboard diff --git a/main.py b/main.py new file mode 100644 index 0000000..d7f3f3b --- /dev/null +++ b/main.py @@ -0,0 +1,26 @@ +import asyncio +from aiogram import executor +import platform +from threading import Thread + +from bot import dispatcher +import ai + + +if platform.system() == 'Windows': + asyncio.set_event_loop_policy( + asyncio.WindowsSelectorEventLoopPolicy(), + ) + +update_statistics_thread = Thread( + target=ai.update_statistics, + daemon=True, +) + + +if __name__ == '__main__': + update_statistics_thread.start() + executor.start_polling( + dispatcher, + skip_updates=True, + ) diff --git a/messages/__init__.py b/messages/__init__.py new file mode 100644 index 0000000..6f5e7dd --- /dev/null +++ b/messages/__init__.py @@ -0,0 +1 @@ +from .messages import start, select_category, select_subcategory, select_date, select_time_range, input_email_address, input_phone_number, input_comment, confirm_order, success_sign_up, select_order, select_operation, reschedule_order_select_date, reschedule_order_select_time_range, reschedule_order_select_time_range, success_reschedule_order, cancel_order, success_cancel_order diff --git a/messages/messages.py b/messages/messages.py new file mode 100644 index 0000000..ef733c8 --- /dev/null +++ b/messages/messages.py @@ -0,0 +1,210 @@ +import api +import database +from fsm import FSM +from models import iso_datetime, Message, FSMContext, InputFile +import keyboards +import re + + +async def start(message: Message, state: FSMContext): + await state.reset_state() + await state.reset_data() + await message.answer( + text='Здравствуйте, %s!\nЧем могу помочь?' % message.from_user.first_name, + reply_markup=keyboards.start(), + ) + await FSM.start.set() + + +async def select_category(message: Message, state: FSMContext): + await message.answer( + text='Выберете категорию.', + reply_markup=await keyboards.categories(), + ) + await FSM.select_category.set() + + +async def select_subcategory(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='%(name)s\n\n%(description)s\n\nВыберете подкатегорию.' % dict(data['category']), + parse_mode='HTML', + reply_markup=await keyboards.subcategories(data['category']), + ) + await FSM.select_subcategory.set() + + +async def select_date(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer_photo( + photo=InputFile('./static/images/%d.%d.png' % ( + data['subcategory'].category.id, + data['subcategory'].id + )), + caption='%(name)s\n\n%(description)s\n\nВыберете дату.' % dict(data['subcategory']), + parse_mode='HTML', + reply_markup=keyboards.dates(data['free_to_order']), + ) + await FSM.select_date.set() + + +async def select_time_range(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Выберете время.', + parse_mode='HTML', + reply_markup=keyboards.time_ranges(data['free_to_order'], data['date']), + ) + await FSM.select_time_range.set() + + +async def input_email_address(message: Message, state: FSMContext): + await message.answer( + text='Введите адрес электронной почты для связи.', + parse_mode='HTML', + reply_markup=keyboards.back(), + ) + await FSM.input_email_address.set() + + +async def input_phone_number(message: Message, state: FSMContext): + await message.answer( + text='Введите номер телефона для связи.', + parse_mode='HTML', + reply_markup=keyboards.back(), + ) + await FSM.input_phone_number.set() + + +async def input_comment(message: Message, state: FSMContext): + await message.answer( + text='Опишите вашу проблему.', + parse_mode='HTML', + reply_markup=keyboards.back(), + ) + await FSM.input_comment.set() + + +async def confirm_order(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Категория: %s - %s.\nДата: %s.\nВремя: %s.\nАдрес электронной почты: %s.\nНомер телефона: %s.\nОписание проблемы:\n%s' % ( + data['category'].name, + data['subcategory'].name, + data['date'].strftime('%d.%m.%Y'), + '%s - %s' % ( + data['time_range'].start_time.strftime('%H:%M'), + data['time_range'].end_time.strftime('%H:%M'), + ), + data['email_address'], + data['phone_number'], + data['comment'], + ), + parse_mode='HTML', + reply_markup=keyboards.order_confirmation(), + ) + await FSM.confirm_order.set() + + +async def success_sign_up(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Обращение %s успешно оформлено!\nВ указанное время с вами свяжется наш специалист.\nСпасибо за обращение!' % data['issue_key'], + parse_mode='HTML', + reply_markup=keyboards.main_menu(), + ) + await FSM.success_sign_up.set() + + +async def select_order(message: Message, state: FSMContext): + issues = await database.read_issues(message.from_user.id) + if len(issues) > 0: + await message.answer( + text='Выберете обращение.', + reply_markup=keyboards.issues(issues), + ) + return await FSM.select_order.set() + await message.answer( + text='У вас нет активных обращений.', + reply_markup=keyboards.main_menu(), + ) + await FSM.no_orders.set() + + +async def select_operation(message: Message, state: FSMContext): + data = await state.get_data() + issue = await api.read_issue(data['issue'].id) + datetime_start = re.match(iso_datetime, issue.fields.customfield_10035) + datetime_end = re.match(iso_datetime, issue.fields.customfield_10036) + await message.answer( + text='Категория: %s.\nДата: %s.\nВремя: %s.\nАдрес электронной почты: %s.\nНомер телефона: %s.\nОписание проблемы:\n%s' % ( + issue.fields.customfield_10033, + '%s.%s.%s' % ( + datetime_start[3], + datetime_start[2], + datetime_start[1], + ), + '%s:%s - %s:%s' % ( + datetime_start[4], + datetime_start[5], + datetime_end[4], + datetime_end[5], + ), + issue.fields.customfield_10037, + issue.fields.customfield_10038, + issue.fields.customfield_10039, + ), + parse_mode='HTML', + reply_markup=keyboards.operations(), + ) + await FSM.select_operation.set() + + +async def reschedule_order_select_date(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Выберете дату.', + parse_mode='HTML', + reply_markup=keyboards.dates(data['free_to_order']), + ) + await FSM.reschedule_order_select_date.set() + + +async def reschedule_order_select_time_range(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Выберете время.', + parse_mode='HTML', + reply_markup=keyboards.time_ranges(data['free_to_order'], data['date']), + ) + await FSM.reschedule_order_select_time_range.set() + + +async def success_reschedule_order(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Обращение %s успешно изменено!\nВ указанное время с вами свяжется наш специалист.\nСпасибо за обращение!' % data['order'].id, + parse_mode='HTML', + reply_markup=keyboards.my_orders(), + ) + await FSM.success_reschedule_order.set() + + +async def cancel_order(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Отменить обращение %s?' % data['issue'].key, + parse_mode='HTML', + reply_markup=keyboards.cancel_order(), + ) + await FSM.cancel_order.set() + + +async def success_cancel_order(message: Message, state: FSMContext): + data = await state.get_data() + await message.answer( + text='Обращение %s успешно отменено!' % data['issue'].key, + parse_mode='HTML', + reply_markup=keyboards.my_orders(), + ) + await FSM.success_cancel_order.set() diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..eb312b8 --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,6 @@ +from aiogram.types import Message +from aiogram.types.reply_keyboard import KeyboardButton +from aiogram.dispatcher import FSMContext +from aiogram.types.input_file import InputFile + +from .models import date, email_address, phone_number, iso_datetime, max_comment_length, ReplyKeyboardMarkup, Category, Subcategory, Executor, TimeRange, FreeToOrder, Order, Issue diff --git a/models/models.py b/models/models.py new file mode 100644 index 0000000..1c9817e --- /dev/null +++ b/models/models.py @@ -0,0 +1,79 @@ +from aiogram.types.reply_keyboard import ReplyKeyboardMarkup as _ReplyKeyboardMarkup +from datetime import date, time, datetime, timedelta +from pydantic import BaseModel +import re +from typing import Literal + + +email_address = re.compile(r'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)') +phone_number = re.compile(r'\D*') +iso_datetime = re.compile(r'(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).(\d{0,6})([+-]?\d{0,2}:?\d{0,2})') +max_comment_length = 1024 + + +class ReplyKeyboardMarkup(_ReplyKeyboardMarkup): + def __init__(self, keyboard=None): + super().__init__( + keyboard=keyboard, + resize_keyboard=True, + ) + + +class Category(BaseModel): + id: int + name: str + description: str + + +class Subcategory(BaseModel): + category: Category + id: int + name: str + description: str + + +class Executor(BaseModel): + id: int + name: str + + +class TimeRange(BaseModel): + id: int + start_time: time + end_time: time + + def __hash__(self): + return self.id + + +class FreeToOrder(BaseModel): + date: date + time_range: TimeRange + executor: Executor + busy_interval: timedelta + + +class Order(BaseModel): + id: int + subcategory: Subcategory + date: date + time_range: TimeRange + executor: Executor + telegram_id: int + email_address: str + phone_number: str + comment: str + start_time: datetime = None + end_time: datetime = None + + +class Issue(BaseModel): + id: int + key: str + status: Literal[ + 'undefined', + 'new', + 'indeterminate', + 'done', + ] + telegram_id: int diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8169339 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +aiogram +jira +psycopg[binary] +pydantic diff --git a/static/images/1.1.png b/static/images/1.1.png new file mode 100644 index 0000000000000000000000000000000000000000..1dad89a9ca7e7cde13db37ec61ad3eecc542d65e GIT binary patch literal 3808 zcmb7H^;Z)N8>O3pAdMir(iD~+xv|Z5T|doy z5Enkl6VwK=GqQ_~R?Ug6L@JQ3#PBY2R_B@B;>A#_&V=hT_^$sS`}vp6#Z`y<;yPb= z-vsW-7-p|B_uptDqhQqOQx#4tOgd!vs&~UBLO?pqx50<_+tPlZlW>bm-S!Chp`3*> zEs@IqHMeLY^5ipuuhum#{W*L9P0fBUx&-x<$0OhR5AhpBhsY@?=n^{VUD!BYf!cj> ze4EmUHh%q~55lvw^pK=^$0UQ`$3S^;v%;y2O$sxlB`{^ZwO>M3R#9r}b<)RkD(Z7d z|N6SH$#K(&{D$&Es{U)0c!4b#a?;xF*FVV-TuM(C|K$b{_O9aQxye=v5;7(MjV_uZ)jIsUS0nuoceh&m@46CYfi(25U*AOK9@ScHQz}Jnl!$y} zVzR%Ti`zDQ>yVzQ%A4k)mZn||G_pDiUL0q56QZVln_~P=k46TUm@sSioFB}Zm?DtF zjTjQ<#IBiLC@SL;HJv2fIN=_%H66&z&U~=Hzgt==ED~N=hG@nE@kn@2RO~ppOhdyT zb6N05PKej!PL)oL-$=~7k7fb7`M52o(`GI_o9%@@2og)1vULcxmZ8$hH)So@NZTrY z;rU1NVEkKa{4tq{_l4%b(C|A#v)aKP+A{)I|v%r zMx#K+Tf^gRHvdPYxMJ|?m3y$u2TNcod5f?v!LpNp>37-;eMIlh9D7!9hvKXwfP7l* z?<%eTCABim!n5X(bR(K|#GqDN9m8Ixwfx}_EX~^L6U>>e%#`I(Zlff~c|xMNbf*Fn z@N!&1&W{V@zFKwi!ZR$Jv!kYkDdZ1n=ho}jY4PS5&RUn$f!Fck8p<{nRhk9HeeX%- zqX@%)3zzu4J6u0GYHB3gVWza3zEHRU?D}^-@5+Y^^7Zw@We`*DwPBS!ykHN z*1lq#Btt>wHhzQ+maOpQS}CFJ1s~u|Vsb*NLfhrq%Yte-&Ae4ru2z%R0M_(6zoh0X zRGYVSJRb#r?J`cKw6rWP^A9fvl!f|F8MH*Dim(n$Zs27XQe^FQw7-jB<~V>l^=HTq zT6PlEB7N%*Z<}<>6R)=3-*MM!URF8~&aq8!@i~K5>QRmS+`xz4;*|7WO%2HTN|*(m zdBr?DyB?=U5+!!-VRQ)Jk{X+83w*+;a^N=@pmc%DTtH**O!U?C4WfKo@8vZR-ei1dl8E0bcyGgPDSTWonZQVBWl zq_K+jSFAXjiZG(fN~TYuK4h3tK#?ZwN`ypFCD|&%E}CGxhzpW;rKl^2%Ty6IBl>_ z2t0AA=PF(&TIWvY4#}tVK~Z~vr!NfMU%iY2q!taiz}@uPfT)Bl2EhRLzIjvo#M80q zIqDhjREcuFv_i|#yKwfK0WvBoYRZTuo`j|)g*P@TgH8?ayHqZVJfxAWTo`fboX^9~ zGGBP4Oa@bEAi-m_qusA6%0i%F7}wCVHk#Gn4N2&Itz@(Zbn^_{xe_G?4CC?ht9CsR z1MXbLex;|UH?|C;OqR@~Wh1uQH$U^?1nCbzw<7RFI((??Wp9OB{dc_h+XHOUe^;mU z+Q|a9(3?CMZq&h=S|#{|3S0(`%`9-n(#JAUK`ZP35PZD7+oVcBAxwUG<_y8XStiWf;0bMQc z-WHM?a>(62l@iVqI&*cz1-tpg`W8JS(j$EJRh1dv+inrqCyrcYpCP%s-f!W1Qykp# zYd+VgE%YjpFNq^;{bvUK%Kq+=XHR-Xd4t0&eWT^rI;<%h4V#wAk>tJtI2Y<4B=8R~ zv*tYA$g=Mw3OpWWMlCcSG$Ms2Bi#mZN5qtU0qJun$IQPltxju(U|xmBr%^1~G#|H- z-&baq%zRqDT@`KpnKyX^8q%2Bl%}a~c*>L{!DMaiBTd<(tTatWF5U!|mXXIqOY&my z26^jj9a-VS2al}eiCU40l9!`dWH)8$Q=_j;gCw+g4G+MbKDLe*Re22((lTOeu}4YM zDa%L03z+Y){L+lZg}v`TSB^Zr+GEY`ptwts;(BMkk>;!;Q-;ZD={ekXjFJKD@0GMj zaVHQEZIl5E|n+q|PlS;(r|($hmkbmMAGJyOgj_2N;Hw5Y)UjQDON| zJ~SU()Og%&R0;dN9Ro28)iZ$vTvw_E2@Wo+?h8^8N)auW@hgC>ZO=ooXMqR-77yeR-dyOLt+#~Stj>sVL?0auZ=LR2{GBzn$$+x4);gu}>wdv^U7%Yls}&(;JG~~6H>U3Q??0<^1mBvBl?vz(~@s(J!}5mXC0v|2Z1f2;nv@og?8IN$VWZ`a5An$)(;D}4=Bw1 zeSS-c=IFQlV`UbyF;&yQf@==q6}1xW?k3oNapwEtp}Br{HEH1nop=ZC+&;A{<7ywN z`*scOG@ul)TxrbTYou78D1CGtmG}c4NGhU-L3N$l3lvwZw1&l}N#U@3N~58C1Ycsl zFbos81qsjW1W&<9T7N_)=i{MDezLQ0aWEfXd-vY@d64FwJ*b#atrdv8>~c1EWhi|S z)2T(I$9AiOSK}Dni?+s%@S&t8bLy#S>K-ofZ%7Jq@@FqCG`t&gq&hr0+Pyhr!**7F zc;%fRACaU1LCxD_Djj%wfch9S)@>NNOVfI0`DZWZ;Ixd4tm2|icft-UH{KDHS5(G9 zvfv`B&hmW?pJf-L&;I#xl+Xc1`2#FP#mu+nr&<^A7OWLHob6itx3qi)&5wQ#4~23h zM{N@SEmkIi?uJ}OQMO9X?fSxWdYo9?K83)y=nn_|bUZ@iTJBwWJ>Y<=wPL2wCTNbD zJV_B{36zYD<&m_M ztDWsBnOOAt((U@_z;)*3)DA^&Fmvlfe_!OK^1P-NJgSJ3;SN#WR%98xkN+bQWy!Kz zc06mC$}sGRI(DO2l%8eIY;<#;j^HUmgo^)k5|_sfB$d6J<@79jiQQW(sFTZXHR$J? zZR)nj9aIW`;E9g%61m&!XM_kowo>laeXk(ba1ziFN-m`Rgl+>XUSXemoqHmb5|qWw zt%j<41O>xIR#~5%cGGmwkIr~^5n;V;&(20DVoGH8!uxWJ44ZF4ng#U|6LH1?`+W|X zl|w_&Tp7YDjvp@&H5#*tR~Dks=w91wq8rrc)O9FND>YTu1%tz4jJI0;C+AAtCuu^( z40KEM^oaW`@qIW0>rPbifYvPcWN{q}!z}=uQTwi6ZyBpHD4BXnT$dYFZ^x4CLUZM- zxnH{eF-blo5Ez_v0h7~=J6!LN;b6NSA9tpWgSE$ggZX7b+bh5AoQmju5N_)vy@Twj zjl}@TjqK~ifBT7%4{|<@1?i;dK9Q&F(Rhg=^8q~AKz`;y*tKt(x%O|5xB&l1ky=@A<>tY~KC# zU#ss!!qU#%_x`gZawm7Ch*@@|viZ=lNefAsk5@Roe{Jw@>Y|qjNaDOoH%>DmvF2Zw zgYE8Jbl+oWZ($V&i>3%*OD=2EiA$N$&FN07Yh az}GdP8jS|!aoo33JRJ=~b%d&8pVv4VC|V0=%+YF z+EjKzfZVH+b9SYm)6p;oaS3K}%|)}3>+XveE@wY+7hLK@U6gw*1p%b^k$wLQbRHAV z&XDaU&~NP+4+JYeKajm$JN;r2t(7Y|p-=%WMPK`@ZPbDL^ez%F@sv9+*7Q%xvL$0_7{Uqk>L2_cBm<`Wcp)(E;PaC{IZlx zyO=d_np>GEf`^xfd#ud5<5(G(mKL8kij%IJf9~aBii<95X#t6V&w}htopnOtW7M>U zhHg>><(OQ}Q39`3If)jBY#X@WPu3fJGFV)lgbBlX$Zb8A3+ooAbiJZ8|94fAj0OF` z`Z~G7o8r-**P7Z|hubLql$6xjY7kFcbX)u#KCx;!-rxGkZGjwVN#D_dQIUk|DvdQA?i6&!f^biK%=UP-8`%(?$_6w{(qwG9&GmJP=SRQeVd0SNEtm*5^K)aNChp=A!E;W|D zfxc*HKvd1`S8vZ>hwgqu98FD`nN7bG-rL**`)(iBCx=eF6l|yl3JAm?2qjV=DabYq z0GL`CbxODlgBcq$(QVy@sf}CLrF8-!(_%uN;l5u)+KiXFwcwRRo??(V3tP+hzx2bL zd9|o2P@#3l^g3z+>vV|Fr7pkz@OQU3c(5daVBDNdwY|OQ?RIRR|7%x7EUE0X%m zPi}Vw#m@Nv+uJ|;nOD#l@d~~bJNM6L`&h+bDk|@ur1A4m#+ArNKMV=$ z(t7}HomM$(OG~4Ri_}w6;+iFz-p(swk7S=$i#K+kwL&Oavt4=W+p}O6#^-}QP4zyr zd~kE!FALzeV6t+w-P5@)61-`BBuhr#{^rKz!6d*eT{*uEB1!W~y6ujd1yOnb2)o~G zx`4bgeCnB<=QRFpyeT=bGv{MDQiul9A#$HIcJW*0P%^!FDk;6iHqgaFz@IDZ-Nz6p zArbZa!%TF~0oFM`GmAOKr6IvsN9P%_5bb_{T^|med~W`)+9mAKu~D;xfq{Wspzia|Q#y4;^`&LciBHXdXj#Gq zUAR$}!%Fcy0q5~j6uO}yNjm>a$NPDR3BZJu<>k6mh>ZX5HQXdnx_7dsj-mM?TdJ~? z0&KiMigqaZ7tn*E>l@#+v!Cp`q7766Bmg8NEgkBgl! z%JZ>*=Z6IyON~?dtd`5nY39Ki9v*%s`+xjBWrUO@S2({1U9QZEG-*@-mF3~`ndA&x z`^6maHN*P$v!?v`UgpiV4An8nq6GOhp@lA^92d*xPr(vDO49sw1VQrWIFc!s<1(UHizlnUe4{-mPHF(jM`oom@1tBc@Ox9er9k{4{;i0C# zEzl9+nt&jysNxO#%loV$!n)9nKwZWlaZN9QjYO7sluSXR2$fYq_~WkkQex zkXPViklH)cCcJyeK0v?b!0+tk^APcKF%SYBigj=@fF6c|x~aa3>pHTmZCv_G&T?+Y zvt(od&P#MPw!z+gnb$cr72&XX(Wy%16IP*(m+3F4#q{gdbRg)!^^=Be^dfTCAQ`}z zElKXla#xG?ZGGVbc^3PM2$csuFwuplX_sfc1Eyel*KI`4es2mIYr*9cwZzzY_#`g+ zZ}W3r{Ol0P6!XqV&U~yDO$ONfyAGBX1ho(W4OSm0g3m5(6aL=_T{>)?ej+uN+qsUsEqaOae&my zLBIR3ig^zW%O%ALR|0X|7D$Xx$QEx8Q(&b*YHBJb>fiV#7ZWpw!b3D%y!V`4xGpnd zelTCg2~;pZHU6bnO16Ix48ChX{j+Fjj9tXxHCsCK%;Bk4=8F4pKd<+^MK?dm`Luqv zz$8wVT)cC$<3O_+i_@gF*=JW;j=ODdC!NImrr=q$xzeNPkGDx$M2I+IeR9{U0X?JG ze$;9&S3qS2uvd}d;$|=1BlwhL?*b-4!E}Rs^5!zoOYHHX*K%h-kOVE$M0wA#kny<_ z*!ds4vrvisRZr5Xsk8%!Xn8r|xKs4QLoQBE;TV&l0X> z#Cb~@6GcOl`i|Y#mjid9eG6GsTkN3d?oMmQG zO3sYRoPK*0CM75qN~8V zAb!u7kc)5^AykHSnj3kG-OQ7n6B>E}Q#a?@w(MX@EqbzB^)$3*|n*uVbnt7$Xsos5*aa;tnOo z-qW3-K1Mlaz5+_)VNXM)t+K8R9d!{o6 zPWV)f_8Q71vVVDd&e2^kt@-xbSMSBUckA*t$r$XP9#Pj*a;GC_0r+vCP|(?~Mx5En zt(~N(kE*@f&iQK3J}hpo<)C0}F10@!P|UpUZCl4PFx{25BGmk8UG#U6;IH88f^XQy zw6!ap1oC<)bXfkB(T0Ze+-=d;XMJnZX`wb!vwXo|cx3^%;d4;huVrr_IA!kx7-JU)%n76Wn{0>sl58|$G_-lf%cb>NP=upi>O(dg0 zG`oGDXAI_3j15v~3y{?r(v-W+-E)@qPA{FWXH0q!_nMiF1N)_0>A;#t{*N@ActgV3 z4vqYt^&PUwPp5cA!BC&4MRewV(<+gIeKQck$~^K;g2rh4xWGws~_$T5X?$w01?Xy!E@7TtvR@gyep z5l_M}1v)VwNV6?u?D@pw{xC>8|CVLq#NWE}z1+c0F78%twCe;5IhHKVS6J&O+J;7K ze5(_b6^keL6b_dOg@Z(6M|Oy^xnuqvVzH$(O#tmPDtS9UHy~5sIo$ zRMPbwY&UEqeXANq&%7noL9JszpT{Zvcoaw{zfi+|ys?zR#m_lNE zgzc$G*233AYR)!afu!%wtBjUJloiRY%_&+U@1!mJ4PPfaFc-ainB(F7a|dobbO|6@ z)1Q=37t%l2anbj#wnCdQJcioFvU00d>PnR`pp5xM)VnOcz8;NNs#WE$|?}oUky>> zggQN8H$L%BT!!bdk7||JMHKsa9Fy)B+KggeO4;3w+T#tr@&LIrqp7_>EbUVwW%kQu z3*T)jUSvdlWpPXMNUQj0E5^FBTy?lI4D^6h^BOr=iq literal 0 HcmV?d00001 diff --git a/static/images/1.3.png b/static/images/1.3.png new file mode 100644 index 0000000000000000000000000000000000000000..2832d925704afa28cdea16353882ac56652f06bb GIT binary patch literal 8788 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 literal 0 HcmV?d00001 diff --git a/static/images/2.1.png b/static/images/2.1.png new file mode 100644 index 0000000000000000000000000000000000000000..ba5209957ae33fadd168e44b5558813284357378 GIT binary patch literal 4553 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 literal 0 HcmV?d00001 diff --git a/static/images/2.2.png b/static/images/2.2.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb91297ab6c80898abc6d25442e920bcad7735a GIT binary patch literal 5006 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 E0Dwim{{R30 literal 0 HcmV?d00001 diff --git a/static/images/2.3.png b/static/images/2.3.png new file mode 100644 index 0000000000000000000000000000000000000000..e6ef4053b551281c998b708f46953da7b05e5827 GIT binary patch literal 3657 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 literal 0 HcmV?d00001 diff --git a/static/images/2.4.png b/static/images/2.4.png new file mode 100644 index 0000000000000000000000000000000000000000..50e70e4e6ddf182c6a510285d4267403796a07a7 GIT binary patch literal 4465 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 Fe*n@A_Dz?H+Iaz(O-ynXiUf*yU$zr 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 literal 0 HcmV?d00001 diff --git a/static/images/3.3.png b/static/images/3.3.png new file mode 100644 index 0000000000000000000000000000000000000000..0d2fa55c38d8314f1573229326766d5e881a8e8c GIT binary patch literal 5234 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 literal 0 HcmV?d00001 diff --git a/static/images/3.4.png b/static/images/3.4.png new file mode 100644 index 0000000000000000000000000000000000000000..8e5689c8c800b2a2dafd81e4e627dc6a0cfc9def GIT binary patch literal 3474 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 literal 0 HcmV?d00001 diff --git a/static/images/3.5.png b/static/images/3.5.png new file mode 100644 index 0000000000000000000000000000000000000000..84d6232fa72cbbe9bd0ac3d83db1aad746e1f3f3 GIT binary patch literal 5798 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 literal 0 HcmV?d00001 diff --git a/static/images/4.1.png b/static/images/4.1.png new file mode 100644 index 0000000000000000000000000000000000000000..ad092882041f834e9f2393423676ccf03aecb041 GIT binary patch literal 3448 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 aV6{${-Y8sN3%>i8z|&IG10z-Jqy7&deOq|| literal 0 HcmV?d00001 diff --git a/static/images/4.5.png b/static/images/4.5.png new file mode 100644 index 0000000000000000000000000000000000000000..cd0305a261c0ea7eb6b5377f39e3ea311ea09112 GIT binary patch literal 4187 zcmbVQ^-~m%(_6ZRx|iDF;7}gEex+#OW4V`2 zWInA%JDA=KvPct*W#bEr4Pzta@OE{KiJ3IP1EiP;Zs+*Lw1E(7g~MxO+65xR44Uy$ zBl8K*znHwN>nALD78Ww~4egIwTL;Q?m_8DktaaK)&y8O?pb`6kv%6b&w7-}-aTweG zRif|k$v+2_Y>Kh@`!vv#6B852#i?>w>4qaD9Q{;yHOZFm zDE@af>&QUYRrEeu{pewHsFO)x$y9U}+J~2IwfZY57QbcHsgq_R3 zb1ko^x%R`7n&p2Ta@%b`+ocm$vpLu_HP*NIp7lQPlNKB$W_!Ud{b}dX_SOo3r@cx~ zFC&#`eIfaCkkzL3K4h?+^&tbXzEIYcdkAGU7fj zEs4F{hPA9EV6mwzsP@P=zV?4jMJm^}+e*8wV&i?xJ^-wjhdcnM0MTuhNw?JUU$Nh- z05!W@?S7o65vDSJr`T-X_V0_8@o`bj0U`#)1`?!(+4;2%=NV6+Y*W7$;D)7KJ4;t7 z=>0q8Tt$FnTPs0d=`WDR)T5_+w-|8VP$A!&M zQxluqoh^P&G^8JO)>-ceV_brn0TatBpLHJqM3047q(71LnSi!kTtF%(UkuXI?P5yM zho^gh*{RZy2AA|7p`7qQwsyB)pXX~-HW>{cUn!~R3pK{s_?}Z{Q8M^i4+^wiW3CsO z-d`OGs;a8$MI{Q}@}-uOc-3|)Huq~lRtU=s_?Qk)U)$@&ea;|bN~yN^XRK?fb$!z~ zUHaI+MRSn3d9jOp{_E-m(#;ic5oYeCo_;@SCnr&uWAX(l?KOW~aMvN>@qpbX)l zN?Mj*B%GA=2Px&_p+bfn&-Xy{5_M%5jEEs#;F0t(Q?jwP!Q zNRgaR>2aJKI9j%U0&-JJ>+5?(lx4O_B)XEVHY>$2H=R1fz$Z)d}+;ad`Na*71DdA&QGfQWJHx%X5S&y|vPWTMkKsc}URp<30odmQc5GEX<; zjYye*{f$b+bPEHu8ga3DY*inE6ho|;sX+{OzeZbglI3m)<&&#UHwzQhMD^WITFc8j zcUgFQf9T=ppU;AsdZAQf=Mh|v%UVJ8Rp;6YrB zfrFIiFOXSZT4&19@z+nHj-9uvVcHYP-1PW49lnY1CXqI`#j z?}0F7(8$VeYjKOoY+DHLBNLyVNiCGaHVveA2H^LNuFa0|=#^zwrFb3mf}aLY1v_na z1XQ<5zz>y}yyPxVvqju`>njcudEly)pJ^Bm8)8ZHZyO#hI-)eC z*bR~*@Wl$Cq3pp_7P+@eodLIb2wH<*s#s5y&eD(3x26sQ={`D!mRS*N0zQcTq{*7H za)HjXm93=ueJ&PdD;vkTQ5oxrgGmSltwG+%_*m!HluYmDvby|8`-BE-b$WhYg!UWF zR;XIaFa@)SHw}4VXORgLCC0zgi$BQ9?c+z0OX1C1W!0+aZXbw+jnhDB+RYh(hEz<@t!?z@ zB}ld+5q*7-SxgHA%z7#^`E)6YoSyd5SUtU(m9uJfR&eVzkn%75<#}tXpHCu<=5Xro z!6lj6e^( zg3NJ_d8xx~0a_z}GzcTGI7iFt+a!xPYE`bL(0e!*%F9CCuRfsq8s+cHYpcDDmkyCr zEkq$^@rK?tH?l|X?Ezi{Bm3zjBB8=NOA`RpztN)piuCSTL54SdF3WK$bSJ8^z?*Dn zsB&5lnUS&4`pX(#zIK0K?d>&yB;DR%gcP$IPOhgi%Yg^-D_#w^UlnX!T{Bn%59wL5 zGCwUYNx?@GR~SUte0}Wy+r(JE#8*dy?D#P|E=CC2NP75E$tW$=VK#ayf)G{odKS*G zQ8-ayGyiFG_3FAJZj2)5u#XT-E5nSc^X0%iiyz39`geOXy}AOC>x5(X6`X=@yDCS_ zW-C31)uC9HU@x-6RX3}18=wH4+!aN}%KBfa^&ogoX~yQ`3gJZV2dXiW;EN)G$z9OAxTEoT@9-SB+0FC{3R2a;oZG5X z%Q{SDCGPJp?pewDo{nF2-@d4AFObcql*gO4tTq3x;O)bEsU^7I+bfW<7EVsj9sd{& zF9x;P2#r}W@<*~Vo!z*&!&7=>Jq5YqHO(xYD6OSNNB5cy&!3&XGeO#h#>=+KFoOWb zq?ZqgEnPXpn-&{v+`3y&ej1Tsg*a|8^B-jttWf&zY+d&&QiAa5H-_Cm9ZFU<*Nf)y z=ixzEEmzDt{1fa!Cw66Z(@LcP{BSFKq&8C*qmk>W2kVOhG>k_2nV}|j$TRl1zJ_cD ztozpBJrl!Q7Xp={>*Vypq@$i&=a4)b%!pSoy#jcQJ+sY^1Wa-aqw z5A=fjlA=kRJ75_YYe~V6U2n2T7LZ$K_%1kPj$F8_Fqi0rmYZUeZ7Ar=1&K@gt=33- z8^RnoyTtuYkMh>Geg}IW+=iY^xVyTRV(`0l?{iO}pqK#)=3J4Brw8@I&{Xfb@U(4g z`5KfcUN_X;ym-mn`f=~7`p<08a^woH56gX<@Z}P6f?-k3!R3529V7`Kv8ln)L%6lB*v7#WC8xT^NG`lmciKGj!spTw-VJ%G4^m06N} zU0HJcFv}e8l;J!)3IrKg{=d6`28foRR)Ix~mme^NID(u^ZSxt^I++ zzXuUcAeJ=G2(qywFd1RsuEg@^{%KzjSe#(0qXl!W?10u>&!xHYwFwT837o}r7yZZ} z&v8)BvO@`v%8%Ok=7`kA*XP<84sS^AgJD~VJzRMdSnA@9I*8u|s+hC~P(OSiL#q9% zgZ3|#J1VxTb+Rr6g!AyEPN-5@7jo8Ak5LLDU-x*ww=V?;`dly(dwPT|_W-Rv*JfT9xih6nz{cH`a0?p;`ALOx^iRxg8^DbRDKxD?PB+#ibEl`yOR@|>ym zSzUR#Ebt1Vv0A3=zwRVUs}9bCQHKbW8SvE9lsz9F?)2x`p4gX?VsO3|`ciukmEdl! zz$t#@Ur8)b0DQl@N*n!{jbPqX#QA=i3XgBvfj2e>Hn^9h_1D-n$kR6iLd-tn6?3JC}WlR`{SzP4z(W h|08#t>RflYaP_KJ;V`3_`-cF|YZa|m)k@Z3{{tyF7t#O# literal 0 HcmV?d00001 diff --git a/static/images/4.6.png b/static/images/4.6.png new file mode 100644 index 0000000000000000000000000000000000000000..d43f6fb0d03f112e30bb580e990c2c6ecac92160 GIT binary patch literal 5383 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 VRF1dG-VZMU8Y()FN@c6i{{icXW!3-y literal 0 HcmV?d00001 diff --git a/static/images/4.7.png b/static/images/4.7.png new file mode 100644 index 0000000000000000000000000000000000000000..c12a9f07acb6f661c8c4edc22fcdc32103efcd40 GIT binary patch literal 6357 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 k