main funtions are ready
issue: ServiceRequest creates when QR code scanned
@@ -52,6 +52,7 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
container_name: django_app
|
container_name: django_app
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
- ./wait-for-it.sh:/wait-for-it.sh
|
- ./wait-for-it.sh:/wait-for-it.sh
|
||||||
@@ -62,6 +63,21 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- web_db_network
|
- web_db_network
|
||||||
|
|
||||||
|
bot:
|
||||||
|
build: .
|
||||||
|
container_name: telegram_bot
|
||||||
|
command: python3 smartsoltech/manage.py start_telegram_bot
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
networks:
|
||||||
|
- web_db_network
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
pgadmin:
|
pgadmin:
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
|
anyio==4.6.0
|
||||||
asgiref==3.8.1
|
asgiref==3.8.1
|
||||||
|
certifi==2024.8.30
|
||||||
Django==5.1.1
|
Django==5.1.1
|
||||||
django-jazzmin==3.0.0
|
django-jazzmin==3.0.0
|
||||||
|
exceptiongroup==1.2.2
|
||||||
|
h11==0.14.0
|
||||||
|
httpcore==1.0.6
|
||||||
|
httpx==0.27.2
|
||||||
|
idna==3.10
|
||||||
pillow==10.4.0
|
pillow==10.4.0
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
python-decouple==3.8
|
python-decouple==3.8
|
||||||
|
python-telegram-bot==21.6
|
||||||
|
qrcode==8.0
|
||||||
|
sniffio==1.3.1
|
||||||
sqlparse==0.5.1
|
sqlparse==0.5.1
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
psycopg2-binary
|
pyTelegramBotAPI
|
||||||
0
smartsoltech/comunication/__init__.py
Normal file
16
smartsoltech/comunication/admin.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# communication/admin.py
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import EmailSettings, TelegramSettings, UserCommunication
|
||||||
|
|
||||||
|
@admin.register(EmailSettings)
|
||||||
|
class EmailSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('smtp_server', 'sender_email', 'use_tls', 'use_ssl')
|
||||||
|
|
||||||
|
@admin.register(TelegramSettings)
|
||||||
|
class TelegramSettingsAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('bot_name', 'bot_token', 'use_polling')
|
||||||
|
|
||||||
|
@admin.register(UserCommunication)
|
||||||
|
class UserCommunicationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('client', 'email', 'phone', 'chat_id')
|
||||||
|
|
||||||
6
smartsoltech/comunication/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ComunicationConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'comunication'
|
||||||
0
smartsoltech/comunication/management/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# comunication/management/commands/start_telegram_bot.py
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from comunication.telegram_bot import TelegramBot
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Starts the Telegram bot'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
bot = TelegramBot()
|
||||||
|
self.stdout.write('Starting Telegram bot polling...')
|
||||||
|
bot.start_bot_polling()
|
||||||
36
smartsoltech/comunication/migrations/0001_initial.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-08 12:20
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CommunicationMethod',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(choices=[('email', 'Email'), ('telegram', 'Telegram')], max_length=50)),
|
||||||
|
('settings', models.JSONField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserCommunication',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('chat_id', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||||
|
('phone', models.CharField(blank=True, max_length=20, null=True)),
|
||||||
|
('preferred_method', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='comunication.communicationmethod')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-08 12:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comunication', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='EmailSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('smtp_server', models.CharField(max_length=255)),
|
||||||
|
('smtp_port', models.PositiveIntegerField()),
|
||||||
|
('sender_email', models.EmailField(max_length=254)),
|
||||||
|
('password', models.CharField(max_length=255)),
|
||||||
|
('use_tls', models.BooleanField(default=True)),
|
||||||
|
('use_ssl', models.BooleanField(default=False)),
|
||||||
|
('display_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TelegramSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('bot_name', models.CharField(max_length=100)),
|
||||||
|
('bot_token', models.CharField(max_length=255)),
|
||||||
|
('webhook_url', models.URLField(blank=True, null=True)),
|
||||||
|
('use_polling', models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-08 12:49
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comunication', '0002_emailsettings_telegramsettings'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='preferred_method',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='user',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='CommunicationMethod',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='UserCommunication',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 00:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comunication', '0003_remove_usercommunication_preferred_method_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserCommunication',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('email', models.EmailField(max_length=254)),
|
||||||
|
('phone', models.CharField(blank=True, max_length=15)),
|
||||||
|
('chat_id', models.CharField(blank=True, max_length=50)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 04:18
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comunication', '0004_usercommunication'),
|
||||||
|
('web', '0005_alter_blogpost_options_alter_category_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='emailsettings',
|
||||||
|
options={'ordering': ['-display_name'], 'verbose_name': 'Параметры E-mail', 'verbose_name_plural': 'Параметры E-mail'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='telegramsettings',
|
||||||
|
options={'ordering': ['-bot_name'], 'verbose_name': 'Параметры Telegram бота', 'verbose_name_plural': 'Параметры Telegram ботов'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='usercommunication',
|
||||||
|
options={'ordering': ['-id'], 'verbose_name': 'Связь с клиентом', 'verbose_name_plural': 'Связи с клиентами'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='client',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='communications', to='web.client', verbose_name='Клиент'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='chat_id',
|
||||||
|
field=models.CharField(blank=True, max_length=50, verbose_name='Telegram Chat ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(max_length=254, verbose_name='Электронная почта'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(blank=True, max_length=15, verbose_name='Телефон'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 04:20
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('comunication', '0005_alter_emailsettings_options_and_more'),
|
||||||
|
('web', '0005_alter_blogpost_options_alter_category_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='usercommunication',
|
||||||
|
name='client',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='communications', to='web.client', verbose_name='Клиент'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
smartsoltech/comunication/migrations/__init__.py
Normal file
50
smartsoltech/comunication/models.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# communication/models.py
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from web.models import Client
|
||||||
|
|
||||||
|
class EmailSettings(models.Model):
|
||||||
|
smtp_server = models.CharField(max_length=255)
|
||||||
|
smtp_port = models.PositiveIntegerField()
|
||||||
|
sender_email = models.EmailField()
|
||||||
|
password = models.CharField(max_length=255)
|
||||||
|
use_tls = models.BooleanField(default=True)
|
||||||
|
use_ssl = models.BooleanField(default=False)
|
||||||
|
display_name = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"SMTP: {self.smtp_server}, Email: {self.sender_email}"
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Параметры E-mail'
|
||||||
|
verbose_name_plural = 'Параметры E-mail'
|
||||||
|
ordering = ['-display_name']
|
||||||
|
|
||||||
|
class TelegramSettings(models.Model):
|
||||||
|
bot_name = models.CharField(max_length=100)
|
||||||
|
bot_token = models.CharField(max_length=255)
|
||||||
|
webhook_url = models.URLField(null=True, blank=True)
|
||||||
|
use_polling = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Telegram Bot: {self.bot_name}"
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Параметры Telegram бота'
|
||||||
|
verbose_name_plural = 'Параметры Telegram ботов'
|
||||||
|
ordering = ['-bot_name']
|
||||||
|
class UserCommunication(models.Model):
|
||||||
|
client = models.ForeignKey(
|
||||||
|
'web.Client', on_delete=models.CASCADE, related_name='communications', verbose_name='Клиент', null=True, blank=True
|
||||||
|
)
|
||||||
|
email = models.EmailField(verbose_name='Электронная почта')
|
||||||
|
phone = models.CharField(max_length=15, blank=True, verbose_name='Телефон')
|
||||||
|
chat_id = models.CharField(max_length=50, blank=True, verbose_name='Telegram Chat ID')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Связь с клиентом'
|
||||||
|
verbose_name_plural = 'Связи с клиентами'
|
||||||
|
ordering = ['-id']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.client:
|
||||||
|
return f"Связь с клиентом: {self.client.first_name} {self.client.last_name} ({self.email})"
|
||||||
|
return f"Связь без клиента ({self.email})"
|
||||||
256
smartsoltech/comunication/telegram_bot.py
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
# import telebot
|
||||||
|
# from decouple import config
|
||||||
|
# from django.shortcuts import get_object_or_404
|
||||||
|
# from web.models import Client, ServiceRequest, Order
|
||||||
|
# from comunication.models import TelegramSettings
|
||||||
|
# import re
|
||||||
|
# import base64
|
||||||
|
# import logging
|
||||||
|
|
||||||
|
# class TelegramBot:
|
||||||
|
# def __init__(self):
|
||||||
|
# # Get bot settings from the database
|
||||||
|
# bot_settings = TelegramSettings.objects.first()
|
||||||
|
# if bot_settings:
|
||||||
|
# TELEGRAM_BOT_TOKEN = bot_settings.bot_token
|
||||||
|
# self.bot = telebot.TeleBot(TELEGRAM_BOT_TOKEN)
|
||||||
|
# else:
|
||||||
|
# raise Exception("Telegram bot settings not found")
|
||||||
|
|
||||||
|
# def start_bot_polling(self):
|
||||||
|
# @self.bot.message_handler(commands=['start'])
|
||||||
|
# def send_welcome(message):
|
||||||
|
# # Проверяем, содержатся ли параметры в команде /start
|
||||||
|
# match = re.match(r'/start request_(\d+)_token_(.*)', message.text)
|
||||||
|
# if match:
|
||||||
|
# self.handle_confirm_command(message, match)
|
||||||
|
# elif message.text.strip() == '/start':
|
||||||
|
# self.bot.reply_to(message, "Ошибка: Некорректная команда. Пожалуйста, используйте ссылку, предоставленную на сайте для регистрации.")
|
||||||
|
# else:
|
||||||
|
# self.bot.reply_to(message, "Здравствуйте! Чем я могу помочь? Вы можете задать вопросы о статусе заявки или заказе.")
|
||||||
|
|
||||||
|
# @self.bot.message_handler(func=lambda message: 'статус заявки' in message.text.lower())
|
||||||
|
# def handle_service_request_status(message):
|
||||||
|
# chat_id = message.chat.id
|
||||||
|
# client = Client.objects.filter(chat_id=chat_id).first()
|
||||||
|
# if client:
|
||||||
|
# service_requests = ServiceRequest.objects.filter(client_email=client.email)
|
||||||
|
# if service_requests.exists():
|
||||||
|
# response = "Ваши заявки:\n"
|
||||||
|
# for req in service_requests:
|
||||||
|
# response += f"Номер заявки: {req.id}, Услуга: {req.service.name}, Дата создания: {req.created_at.strftime('%d-%m-%Y')}\n"
|
||||||
|
# else:
|
||||||
|
# response = "У вас нет активных заявок."
|
||||||
|
# else:
|
||||||
|
# response = "Клиент не найден. Пожалуйста, зарегистрируйтесь."
|
||||||
|
# self.bot.reply_to(message, response)
|
||||||
|
|
||||||
|
# @self.bot.message_handler(func=lambda message: 'статус заказа' in message.text.lower())
|
||||||
|
# def handle_order_status(message):
|
||||||
|
# chat_id = message.chat.id
|
||||||
|
# client = Client.objects.filter(chat_id=chat_id).first()
|
||||||
|
# if client:
|
||||||
|
# orders = Order.objects.filter(client=client)
|
||||||
|
# if orders.exists():
|
||||||
|
# response = "Ваши заказы:\n"
|
||||||
|
# for order in orders:
|
||||||
|
# response += f"Номер заказа: {order.id}, Услуга: {order.service.name}, Статус: {order.get_status_display()}\n"
|
||||||
|
# else:
|
||||||
|
# response = "У вас нет активных заказов."
|
||||||
|
# else:
|
||||||
|
# response = "Клиент не найден. Пожалуйста, зарегистрируйтесь."
|
||||||
|
# self.bot.reply_to(message, response)
|
||||||
|
|
||||||
|
# self.bot.polling(non_stop=True)
|
||||||
|
|
||||||
|
# def handle_confirm_command(self, message, match=None):
|
||||||
|
# chat_id = message.chat.id
|
||||||
|
# if not match:
|
||||||
|
# match = re.match(r'/start request_(\d+)_token_(.*)', message.text)
|
||||||
|
# if match:
|
||||||
|
# request_id = match.group(1)
|
||||||
|
# encoded_token = match.group(2)
|
||||||
|
|
||||||
|
# # Декодируем токен из base64
|
||||||
|
# try:
|
||||||
|
# token = base64.urlsafe_b64decode(encoded_token + '==').decode('utf-8')
|
||||||
|
# logging.info(f"Декодированный токен: {token}")
|
||||||
|
# except Exception as e:
|
||||||
|
# logging.error(f"Ошибка при декодировании токена: {e}")
|
||||||
|
# self.bot.send_message(chat_id, "Ошибка: Некорректный токен. Пожалуйста, повторите попытку позже.")
|
||||||
|
# return
|
||||||
|
|
||||||
|
# # Получаем заявку по ID и токену
|
||||||
|
# service_request = ServiceRequest.objects.filter(id=request_id, token=token).first()
|
||||||
|
# if service_request:
|
||||||
|
# # Обновляем chat_id клиента
|
||||||
|
# service_request.chat_id = chat_id
|
||||||
|
# service_request.client_name = message.from_user.first_name
|
||||||
|
# service_request.save()
|
||||||
|
|
||||||
|
# response_message = (
|
||||||
|
# f"Здравствуйте, {message.from_user.first_name}!\n"
|
||||||
|
# f"Ваша заявка на услугу успешно зарегистрирована. "
|
||||||
|
# f"Пожалуйста, вернитесь на сайт для продолжения оформления."
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# response_message = "Ошибка: Неверная заявка или токен. Пожалуйста, проверьте ссылку."
|
||||||
|
|
||||||
|
# self.bot.send_message(chat_id, response_message)
|
||||||
|
# else:
|
||||||
|
# response_message = "Ошибка: Некорректная команда. Пожалуйста, используйте ссылку, предоставленную на сайте для регистрации."
|
||||||
|
# self.bot.send_message(chat_id, response_message)
|
||||||
|
|
||||||
|
# def send_telegram_message(self, client_id, service_request_id, custom_message, order_id=None):
|
||||||
|
# # Get the client and service request from the database
|
||||||
|
# client = get_object_or_404(Client, pk=client_id)
|
||||||
|
# service_request = get_object_or_404(ServiceRequest, pk=service_request_id)
|
||||||
|
# chat_id = client.chat_id
|
||||||
|
|
||||||
|
# # Build the message content
|
||||||
|
# message = f"Здравствуйте, {client.first_name} {client.last_name}!\n"
|
||||||
|
# message += custom_message
|
||||||
|
|
||||||
|
# if order_id:
|
||||||
|
# order = get_object_or_404(Order, pk=order_id)
|
||||||
|
# message += f"\n\nДетали заказа:\nУслуга: {order.service.name}\nСтатус: {order.get_status_display()}\n"
|
||||||
|
|
||||||
|
# # Add service request details
|
||||||
|
# message += f"\nНомер заявки: {service_request.id}\nДата создания заявки: {service_request.created_at.strftime('%d-%m-%Y')}\n"
|
||||||
|
|
||||||
|
# # Send the message using the bot
|
||||||
|
# try:
|
||||||
|
# self.bot.send_message(chat_id, message)
|
||||||
|
# except Exception as e:
|
||||||
|
# logging.error(f"Ошибка при отправке сообщения в Telegram: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
import telebot
|
||||||
|
from decouple import config
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from web.models import Client, ServiceRequest, Order
|
||||||
|
from comunication.models import TelegramSettings
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class TelegramBot:
|
||||||
|
def __init__(self):
|
||||||
|
# Get bot settings from the database
|
||||||
|
bot_settings = TelegramSettings.objects.first()
|
||||||
|
if bot_settings:
|
||||||
|
TELEGRAM_BOT_TOKEN = bot_settings.bot_token
|
||||||
|
self.bot = telebot.TeleBot(TELEGRAM_BOT_TOKEN)
|
||||||
|
else:
|
||||||
|
raise Exception("Telegram bot settings not found")
|
||||||
|
|
||||||
|
def start_bot_polling(self):
|
||||||
|
@self.bot.message_handler(commands=['start'])
|
||||||
|
def send_welcome(message):
|
||||||
|
# Проверяем, содержатся ли параметры в команде /start
|
||||||
|
match = re.match(r'/start request_(\d+)_token_(.*)', message.text)
|
||||||
|
if match:
|
||||||
|
self.handle_confirm_command(message, match)
|
||||||
|
elif message.text.strip() == '/start':
|
||||||
|
self.bot.reply_to(message, "Ошибка: Некорректная команда. Пожалуйста, используйте ссылку, предоставленную на сайте для регистрации.")
|
||||||
|
else:
|
||||||
|
self.bot.reply_to(message, "Здравствуйте! Пожалуйста, используйте команду /start с корректными параметрами для подтверждения регистрации.")
|
||||||
|
|
||||||
|
@self.bot.message_handler(func=lambda message: 'статус заявки' in message.text.lower())
|
||||||
|
def handle_service_request_status(message):
|
||||||
|
chat_id = message.chat.id
|
||||||
|
client = Client.objects.filter(chat_id=chat_id).first()
|
||||||
|
if client:
|
||||||
|
service_requests = ServiceRequest.objects.filter(client_email=client.email)
|
||||||
|
if service_requests.exists():
|
||||||
|
response = "Ваши заявки:\n"
|
||||||
|
for req in service_requests:
|
||||||
|
response += f"Номер заявки: {req.id}, Услуга: {req.service.name}, Дата создания: {req.created_at.strftime('%d-%m-%Y')}\n"
|
||||||
|
else:
|
||||||
|
response = "У вас нет активных заявок."
|
||||||
|
else:
|
||||||
|
response = "Клиент не найден. Пожалуйста, зарегистрируйтесь."
|
||||||
|
self.bot.reply_to(message, response)
|
||||||
|
|
||||||
|
@self.bot.message_handler(func=lambda message: 'статус заказа' in message.text.lower())
|
||||||
|
def handle_order_status(message):
|
||||||
|
chat_id = message.chat.id
|
||||||
|
client = Client.objects.filter(chat_id=chat_id).first()
|
||||||
|
if client:
|
||||||
|
orders = Order.objects.filter(client=client)
|
||||||
|
if orders.exists():
|
||||||
|
response = "Ваши заказы:\n"
|
||||||
|
for order in orders:
|
||||||
|
response += f"Номер заказа: {order.id}, Услуга: {order.service.name}, Статус: {order.get_status_display()}\n"
|
||||||
|
else:
|
||||||
|
response = "У вас нет активных заказов."
|
||||||
|
else:
|
||||||
|
response = "Клиент не найден. Пожалуйста, зарегистрируйтесь."
|
||||||
|
self.bot.reply_to(message, response)
|
||||||
|
|
||||||
|
self.bot.polling(non_stop=True)
|
||||||
|
|
||||||
|
def handle_confirm_command(self, message, match=None):
|
||||||
|
chat_id = message.chat.id
|
||||||
|
if not match:
|
||||||
|
match = re.match(r'/start request_(\d+)_token_(.*)', message.text)
|
||||||
|
if match:
|
||||||
|
request_id = match.group(1)
|
||||||
|
encoded_token = match.group(2)
|
||||||
|
|
||||||
|
# Декодируем токен из base64
|
||||||
|
try:
|
||||||
|
token = base64.urlsafe_b64decode(encoded_token + '==').decode('utf-8')
|
||||||
|
logging.info(f"Декодированный токен: {token}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при декодировании токена: {e}")
|
||||||
|
self.bot.send_message(chat_id, "Ошибка: Некорректный токен. Пожалуйста, повторите попытку позже.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Получаем заявку по ID и токену
|
||||||
|
service_request = ServiceRequest.objects.filter(id=request_id, token=token).first()
|
||||||
|
if service_request:
|
||||||
|
# Обновляем chat_id клиента
|
||||||
|
service_request.chat_id = chat_id
|
||||||
|
service_request.client_name = message.from_user.first_name
|
||||||
|
service_request.save()
|
||||||
|
|
||||||
|
response_message = (
|
||||||
|
f"Здравствуйте, {message.from_user.first_name}!\n"
|
||||||
|
f"Ваш Telegram аккаунт успешно подтвержден. Пожалуйста, вернитесь на сайт для заполнения остальных данных."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
response_message = "Ошибка: Неверная заявка или токен. Пожалуйста, проверьте ссылку."
|
||||||
|
|
||||||
|
self.bot.send_message(chat_id, response_message)
|
||||||
|
else:
|
||||||
|
response_message = "Ошибка: Некорректная команда. Пожалуйста, используйте ссылку, предоставленную на сайте для регистрации."
|
||||||
|
self.bot.send_message(chat_id, response_message)
|
||||||
|
|
||||||
|
def send_telegram_message(self, client_id, service_request_id, custom_message, order_id=None):
|
||||||
|
# Get the client and service request from the database
|
||||||
|
client = get_object_or_404(Client, pk=client_id)
|
||||||
|
service_request = get_object_or_404(ServiceRequest, pk=service_request_id)
|
||||||
|
chat_id = client.chat_id
|
||||||
|
|
||||||
|
# Build the message content
|
||||||
|
message = f"Здравствуйте, {client.first_name} {client.last_name}!\n"
|
||||||
|
message += custom_message
|
||||||
|
|
||||||
|
if order_id:
|
||||||
|
order = get_object_or_404(Order, pk=order_id)
|
||||||
|
message += f"\n\nДетали заказа:\nУслуга: {order.service.name}\nСтатус: {order.get_status_display()}\n"
|
||||||
|
|
||||||
|
# Add service request details
|
||||||
|
message += f"\nНомер заявки: {service_request.id}\nДата создания заявки: {service_request.created_at.strftime('%d-%m-%Y')}\n"
|
||||||
|
|
||||||
|
# Send the message using the bot
|
||||||
|
try:
|
||||||
|
self.bot.send_message(chat_id, message)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при отправке сообщения в Telegram: {e}")
|
||||||
|
|
||||||
|
# Example usage:
|
||||||
|
# bot = TelegramBot()
|
||||||
|
# bot.start_bot_polling()
|
||||||
|
# bot.send_telegram_message(client_id=1, service_request_id=1, custom_message="Ваши данные для входа на сайт.")
|
||||||
3
smartsoltech/comunication/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
smartsoltech/comunication/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
BIN
smartsoltech/media/static/img/services/1_ym12Lh9.png
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
smartsoltech/media/static/img/services/2_VlpsUXb.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
smartsoltech/media/static/img/services/4_MIdyabb.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
@@ -44,6 +44,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'web',
|
'web',
|
||||||
|
'comunication'
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|||||||
BIN
smartsoltech/static/qr_codes/request_277.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
smartsoltech/static/qr_codes/request_278.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_279.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_280.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_281.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_282.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_283.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
smartsoltech/static/qr_codes/request_284.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_285.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_286.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_287.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
smartsoltech/static/qr_codes/request_288.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_289.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_290.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_291.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_292.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_293.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
smartsoltech/static/qr_codes/request_294.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Service, Project, Client, Order, Review, BlogPost, Category
|
from .models import Service, Project, Client, Order, Review, BlogPost, Category, ServiceRequest
|
||||||
from .forms import ProjectForm
|
from .forms import ProjectForm
|
||||||
|
|
||||||
@admin.register(Service)
|
@admin.register(Service)
|
||||||
@@ -41,3 +41,8 @@ class CategoryAdmin(admin.ModelAdmin):
|
|||||||
list_display = ('name','description')
|
list_display = ('name','description')
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
@admin.register(ServiceRequest)
|
||||||
|
class ServiceRequestAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('service','token', 'chat_id','client_name', 'client_email', 'client_phone', 'created_at')
|
||||||
|
search_fields = ('service','token','client_name', 'client_email', 'client_phone')
|
||||||
|
list_filter = ('service','token','client_name', 'client_phone')
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-06 07:28
|
# Generated by Django 5.1.1 on 2024-10-08 13:56
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=100)),
|
('name', models.CharField(max_length=100)),
|
||||||
|
('description', models.TextField(default='Описание категории')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -35,21 +37,33 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('first_name', models.CharField(max_length=100)),
|
('first_name', models.CharField(max_length=100)),
|
||||||
('last_name', models.CharField(max_length=100)),
|
('last_name', models.CharField(max_length=100)),
|
||||||
('email', models.EmailField(max_length=254)),
|
('email', models.EmailField(max_length=254, unique=True)),
|
||||||
('phone_number', models.CharField(max_length=15)),
|
('phone_number', models.CharField(max_length=15, unique=True)),
|
||||||
('image', models.ImageField(blank=True, null=True, upload_to='static/img/customer/')),
|
('image', models.ImageField(blank=True, null=True, upload_to='static/img/customer/')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Order',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('message', models.TextField(blank=True, null=True)),
|
||||||
|
('order_date', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('status', models.CharField(choices=[('pending', 'Pending'), ('in_progress', 'In Progress'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending', max_length=50)),
|
||||||
|
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='web.client')),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Project',
|
name='Project',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=200)),
|
('name', models.CharField(max_length=200)),
|
||||||
('description', models.TextField()),
|
('description', models.TextField(default='Описание проекта')),
|
||||||
('completion_date', models.DateField()),
|
('completion_date', models.DateField(blank=True, null=True)),
|
||||||
('image', models.ImageField(blank=True, null=True, upload_to='static/img/project/')),
|
('image', models.ImageField(blank=True, null=True, upload_to='static/img/project/')),
|
||||||
('category', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='web.category')),
|
('status', models.CharField(choices=[('in_progress', 'In Progress'), ('completed', 'Completed')], default='in_progress', max_length=50)),
|
||||||
|
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='web.category')),
|
||||||
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='web.client')),
|
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='web.client')),
|
||||||
|
('order', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project', to='web.order')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -57,10 +71,10 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=200)),
|
('name', models.CharField(max_length=200)),
|
||||||
('description', models.TextField()),
|
('description', models.TextField(default='Описание услуги')),
|
||||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
('image', models.ImageField(blank=True, null=True, upload_to='static/img/services/')),
|
('image', models.ImageField(blank=True, null=True, upload_to='static/img/services/')),
|
||||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='services', to='web.category')),
|
('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='services', to='web.category')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -72,17 +86,30 @@ class Migration(migrations.Migration):
|
|||||||
('review_date', models.DateTimeField(auto_now_add=True)),
|
('review_date', models.DateTimeField(auto_now_add=True)),
|
||||||
('image', models.ImageField(blank=True, null=True, upload_to='static/img/review/')),
|
('image', models.ImageField(blank=True, null=True, upload_to='static/img/review/')),
|
||||||
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.client')),
|
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.client')),
|
||||||
|
('project', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.project')),
|
||||||
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.service')),
|
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.service')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='service',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='web.service'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='order',
|
||||||
|
name='service',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='web.service'),
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Order',
|
name='ServiceRequest',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('order_date', models.DateTimeField(auto_now_add=True)),
|
('client_name', models.CharField(max_length=100)),
|
||||||
('status', models.CharField(choices=[('pending', 'Pending'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], max_length=50)),
|
('client_email', models.EmailField(max_length=254)),
|
||||||
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='web.client')),
|
('client_phone', models.CharField(max_length=20)),
|
||||||
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='web.service')),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('token', models.UUIDField(default=uuid.uuid4, unique=True)),
|
||||||
|
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.service')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-06 07:33
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='category',
|
|
||||||
name='description',
|
|
||||||
field=models.TextField(default=1),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 03:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='client',
|
||||||
|
name='chat_id',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='servicerequest',
|
||||||
|
name='chat_id',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
21
smartsoltech/web/migrations/0003_client_user.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 03:54
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0002_client_chat_id_servicerequest_chat_id'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='client',
|
||||||
|
name='user',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='client_profile', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-06 09:01
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0002_category_description'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='review',
|
|
||||||
name='project',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.project'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='web.category'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='review',
|
|
||||||
name='service',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.service'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 03:56
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0003_client_user'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='order',
|
||||||
|
options={'ordering': ['-order_date'], 'verbose_name': 'Заказ', 'verbose_name_plural': 'Заказы'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='client',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_orders', to='web.client'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='service',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_orders', to='web.service'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-06 09:08
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0003_review_project_alter_project_category_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='web.category'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='service',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='services', to='web.category'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Generated by Django 5.1.1 on 2024-10-13 04:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('web', '0004_alter_order_options_alter_order_client_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='blogpost',
|
||||||
|
options={'ordering': ['-published_date'], 'verbose_name': 'Блог', 'verbose_name_plural': 'Блоги'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='category',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Категория', 'verbose_name_plural': 'Категории'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='client',
|
||||||
|
options={'ordering': ['last_name', 'first_name'], 'verbose_name': 'Клиент', 'verbose_name_plural': 'Клиенты'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='project',
|
||||||
|
options={'ordering': ['-completion_date'], 'verbose_name': 'Проект', 'verbose_name_plural': 'Проекты'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='review',
|
||||||
|
options={'ordering': ['-review_date'], 'verbose_name': 'Отзыв', 'verbose_name_plural': 'Отзывы'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='service',
|
||||||
|
options={'ordering': ['name'], 'verbose_name': 'Услуга', 'verbose_name_plural': 'Услуги'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='servicerequest',
|
||||||
|
options={'ordering': ['-created_at'], 'verbose_name': 'Заявка на услугу', 'verbose_name_plural': 'Заявки на услуги'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('pending', 'Ожидание'), ('in_progress', 'В процессе'), ('completed', 'Завершен'), ('cancelled', 'Отменён')], default='pending', max_length=50),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='project',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('in_progress', 'В процессе'), ('completed', 'Завершен')], default='in_progress', max_length=50),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-06 10:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0004_alter_project_category_alter_service_category'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='order',
|
|
||||||
name='client_email',
|
|
||||||
field=models.EmailField(default='notprovided@example.com', max_length=254),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='order',
|
|
||||||
name='client_phone',
|
|
||||||
field=models.CharField(default='unknown', max_length=15),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='order',
|
|
||||||
name='message',
|
|
||||||
field=models.TextField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='order',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('pending', 'Pending'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending', max_length=50),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-07 11:23
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0005_order_client_email_order_client_phone_order_message_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='order',
|
|
||||||
name='client_email',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='order',
|
|
||||||
name='client_phone',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='order',
|
|
||||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project', to='web.order'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='service',
|
|
||||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='web.service'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('in_progress', 'In Progress'), ('completed', 'Completed')], default='in_progress', max_length=50),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='order',
|
|
||||||
name='status',
|
|
||||||
field=models.CharField(choices=[('pending', 'Pending'), ('in_progress', 'In Progress'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending', max_length=50),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='category',
|
|
||||||
field=models.CharField(default=1, max_length=100),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='completion_date',
|
|
||||||
field=models.DateField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='review',
|
|
||||||
name='service',
|
|
||||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='web.service'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-07 11:31
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0006_remove_order_client_email_remove_order_client_phone_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='project',
|
|
||||||
name='category',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.1.1 on 2024-10-07 11:43
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('web', '0007_remove_project_category'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='category',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='web.category'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,21 +1,32 @@
|
|||||||
# web/models.py
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser, User
|
||||||
|
import uuid
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
description = models.TextField()
|
description = models.TextField(default='Описание категории')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Категория'
|
||||||
|
verbose_name_plural = 'Категории'
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Service(models.Model):
|
class Service(models.Model):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
description = models.TextField()
|
description = models.TextField(default='Описание услуги')
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='services')
|
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='services')
|
||||||
image = models.ImageField(upload_to='static/img/services/', blank=True, null=True)
|
image = models.ImageField(upload_to='static/img/services/', blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Услуга'
|
||||||
|
verbose_name_plural = 'Услуги'
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -29,14 +40,21 @@ class Service(models.Model):
|
|||||||
return self.reviews.count()
|
return self.reviews.count()
|
||||||
|
|
||||||
class Client(models.Model):
|
class Client(models.Model):
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='client_profile', null=True, blank=True)
|
||||||
first_name = models.CharField(max_length=100)
|
first_name = models.CharField(max_length=100)
|
||||||
last_name = models.CharField(max_length=100)
|
last_name = models.CharField(max_length=100)
|
||||||
email = models.EmailField()
|
email = models.EmailField(unique=True)
|
||||||
phone_number = models.CharField(max_length=15)
|
phone_number = models.CharField(max_length=15, unique=True)
|
||||||
image = models.ImageField(upload_to='static/img/customer/', blank=True, null=True)
|
image = models.ImageField(upload_to='static/img/customer/', blank=True, null=True)
|
||||||
|
chat_id = models.CharField(max_length=100, blank=True, null=True) # Telegram chat ID
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Клиент'
|
||||||
|
verbose_name_plural = 'Клиенты'
|
||||||
|
ordering = ['last_name', 'first_name']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.first_name} {self.last_name}"
|
return f"{self.first_name} {self.last_name} {self.chat_id}"
|
||||||
|
|
||||||
class BlogPost(models.Model):
|
class BlogPost(models.Model):
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
@@ -44,29 +62,59 @@ class BlogPost(models.Model):
|
|||||||
published_date = models.DateTimeField(auto_now_add=True)
|
published_date = models.DateTimeField(auto_now_add=True)
|
||||||
image = models.ImageField(upload_to='static/img/blog/', blank=True, null=True)
|
image = models.ImageField(upload_to='static/img/blog/', blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Блог'
|
||||||
|
verbose_name_plural = 'Блоги'
|
||||||
|
ordering = ['-published_date']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='orders')
|
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='related_orders')
|
||||||
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='orders')
|
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='related_orders')
|
||||||
message = models.TextField(blank=True, null=True)
|
message = models.TextField(blank=True, null=True)
|
||||||
order_date = models.DateTimeField(auto_now_add=True)
|
order_date = models.DateTimeField(auto_now_add=True)
|
||||||
status = models.CharField(max_length=50, choices=[('pending', 'Pending'), ('in_progress', 'In Progress'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='pending')
|
status = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=[
|
||||||
|
('pending', 'Ожидание'),
|
||||||
|
('in_progress', 'В процессе'),
|
||||||
|
('completed', 'Завершен'),
|
||||||
|
('cancelled', 'Отменён')
|
||||||
|
],
|
||||||
|
default='pending'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['-order_date']
|
||||||
|
verbose_name = 'Заказ'
|
||||||
|
verbose_name_plural = 'Заказы'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Order #{self.id} by {self.client.first_name}"
|
return f"Order #{self.id} by {self.client.first_name}"
|
||||||
|
|
||||||
|
def is_completed(self):
|
||||||
|
return self.status == 'completed'
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('order_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
description = models.TextField()
|
description = models.TextField(default='Описание проекта')
|
||||||
completion_date = models.DateField(blank=True, null=True)
|
completion_date = models.DateField(blank=True, null=True)
|
||||||
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='projects')
|
client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name='projects')
|
||||||
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='projects')
|
service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='projects')
|
||||||
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='project', null=True, blank=True)
|
order = models.OneToOneField(Order, on_delete=models.CASCADE, related_name='project', null=True, blank=True)
|
||||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True, blank=True)
|
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
image = models.ImageField(upload_to='static/img/project/', blank=True, null=True)
|
image = models.ImageField(upload_to='static/img/project/', blank=True, null=True)
|
||||||
status = models.CharField(max_length=50, choices=[('in_progress', 'In Progress'), ('completed', 'Completed')], default='in_progress')
|
status = models.CharField(max_length=50, choices=[('in_progress', 'В процессе'), ('completed', 'Завершен')], default='in_progress')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Проект'
|
||||||
|
verbose_name_plural = 'Проекты'
|
||||||
|
ordering = ['-completion_date']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@@ -80,19 +128,27 @@ class Review(models.Model):
|
|||||||
review_date = models.DateTimeField(auto_now_add=True)
|
review_date = models.DateTimeField(auto_now_add=True)
|
||||||
image = models.ImageField(upload_to='static/img/review/', blank=True, null=True)
|
image = models.ImageField(upload_to='static/img/review/', blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Отзыв'
|
||||||
|
verbose_name_plural = 'Отзывы'
|
||||||
|
ordering = ['-review_date']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Review by {self.client.first_name} {self.client.last_name} for {self.service.name}"
|
return f"Review by {self.client.first_name} {self.client.last_name} for {self.service.name}"
|
||||||
|
|
||||||
COMMUNICATION_METHODS = [
|
|
||||||
('email', 'Email'),
|
|
||||||
('telegram', 'Telegram'),
|
|
||||||
('sms', 'SMS'),
|
|
||||||
]
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class ServiceRequest(models.Model):
|
||||||
telegram_id = models.CharField(max_length=50, blank=True, null=True, unique=True)
|
service = models.ForeignKey(Service, on_delete=models.CASCADE)
|
||||||
preferred_communication = models.CharField(
|
client_name = models.CharField(max_length=100)
|
||||||
max_length=20,
|
client_email = models.EmailField()
|
||||||
choices=COMMUNICATION_METHODS,
|
client_phone = models.CharField(max_length=20)
|
||||||
default='email',
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
)
|
token = models.UUIDField(default=uuid.uuid4, unique=True) # Генерация уникального токена
|
||||||
|
chat_id = models.CharField(max_length=100, blank=True, null=True) # Telegram chat ID
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Заявка на услугу'
|
||||||
|
verbose_name_plural = 'Заявки на услуги'
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Request for {self.service.name} by {self.client_name}"
|
||||||
13
smartsoltech/web/templates/web/client_orders.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'web/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Мои заказы</h2>
|
||||||
|
<ul>
|
||||||
|
{% for order in orders %}
|
||||||
|
<li>
|
||||||
|
<strong>Заказ №{{ order.id }}:</strong> {{ order.service.name }} - Статус: {{ order.get_status_display }}
|
||||||
|
<a href="{% url 'order_detail' order.pk %}">Подробнее</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
29
smartsoltech/web/templates/web/complete_registration.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'web/base.html' %}
|
||||||
|
<!-- web/templates/web/complete_registration_basic.html -->
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% include 'web/header.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<h1>Регистрация</h1>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_name">Имя</label>
|
||||||
|
<input type="text" id="client_name" name="client_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_email">Email</label>
|
||||||
|
<input type="email" id="client_email" name="client_email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_phone">Телефон</label>
|
||||||
|
<input type="tel" id="client_phone" name="client_phone" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- Подключение JavaScript файлов из static -->
|
||||||
|
<script src="{% static 'assets/js/script.min.js' %}"></script>
|
||||||
|
{% endblock%}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends 'web/base.html' %}
|
||||||
|
<!-- web/templates/web/complete_registration_basic.html -->
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% include 'web/header.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<h1>Регистрация</h1>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_name">Имя</label>
|
||||||
|
<input type="text" id="client_name" name="client_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_email">Email</label>
|
||||||
|
<input type="email" id="client_email" name="client_email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="client_phone">Телефон</label>
|
||||||
|
<input type="tel" id="client_phone" name="client_phone" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- Подключение JavaScript файлов из static -->
|
||||||
|
<script src="{% static 'assets/js/script.min.js' %}"></script>
|
||||||
|
{% endblock%}
|
||||||
64
smartsoltech/web/templates/web/create_service_request.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<!-- web/templates/web/modal_order_form.html -->
|
||||||
|
<div id="orderModal" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Оформление заявки на услугу</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="orderForm" method="post" action="{% url 'create_service_request' service_id=service.pk %}">
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label">Имя</label>
|
||||||
|
<input id="client_name" class="form-control" type="text" name="client_name" required minlength="2" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label">Телефон</label>
|
||||||
|
<input id="client_phone" class="form-control" type="tel" name="client_phone" required pattern="^\+?[0-9\s\-]{7,15}$" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label">Адрес электронной почты</label>
|
||||||
|
<input id="client_email" class="form-control" type="email" name="client_email" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3 text-center">
|
||||||
|
<label class="form-label">Сканируйте QR код для регистрации в Telegram боте</label>
|
||||||
|
<div id="qrCodeContainer">
|
||||||
|
<img src="{{ qr_code }}" alt="QR код для Telegram бота" class="img-fluid" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<a href="{{ registration_link }}" target="_blank">Перейдите по этой ссылке для регистрации в Telegram боте</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||||
|
<button type="submit" class="btn btn-primary" id="submitButton" disabled>Отправить заявку</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const checkVerificationStatus = () => {
|
||||||
|
fetch(`/service/request_status/{{ service_request_id }}/`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.is_verified) {
|
||||||
|
document.getElementById('submitButton').disabled = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Ошибка при проверке статуса:', error));
|
||||||
|
};
|
||||||
|
|
||||||
|
setInterval(checkVerificationStatus, 5000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,36 +1,214 @@
|
|||||||
<!-- web/templates/web/modal_order_form.html -->
|
<!DOCTYPE html>
|
||||||
<div id="orderModal" class="modal fade" tabindex="-1">
|
<html lang="ru">
|
||||||
<div class="modal-dialog">
|
<head>
|
||||||
<div class="modal-content">
|
<meta charset="UTF-8">
|
||||||
<div class="modal-header">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<h5 class="modal-title">Оформление заявки на услугу</h5>
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||||
|
<title>Модальное окно для заявки на услугу</title>
|
||||||
|
<style>
|
||||||
|
/* Стили для модального окна */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fefefe;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close:hover,
|
||||||
|
.close:focus {
|
||||||
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qrCodeImg {
|
||||||
|
display: block;
|
||||||
|
margin: 20px auto;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Модальное окно -->
|
||||||
|
<div id="serviceModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close">×</span>
|
||||||
|
<h4>Заполните заявку на услугу</h4>
|
||||||
|
<p>QR-код для завершения регистрации:</p>
|
||||||
|
<img id="qrCodeImg" src="" alt="QR Code">
|
||||||
|
<form id="serviceRequestForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="clientName">Ваше имя:</label>
|
||||||
|
<input type="text" class="form-control" id="clientName" name="client_name" placeholder="Введите ваше имя" required minlength="2" maxlength="50" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="form-group">
|
||||||
<form id="orderForm" method="post" action="{% url 'create_order' pk=service.pk %}">
|
<label for="clientChatId">Ваш chat ID:</label>
|
||||||
{% csrf_token %}
|
<input type="text" class="form-control" id="clientChatId" name="client_chat_id" placeholder="Ваш chat ID" readonly>
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label class="form-label">Имя</label>
|
|
||||||
<input id="client_name" class="form-control" type="text" name="client_name" required minlength="2" maxlength="50" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label class="form-label">Телефон</label>
|
|
||||||
<input id="client_phone" class="form-control" type="tel" name="client_phone" required pattern="^\+?[0-9\s\-]{7,15}$" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label class="form-label">Адрес электронной почты</label>
|
|
||||||
<input id="client_email" class="form-control" type="email" name="client_email" required />
|
|
||||||
</div>
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label class="form-label">Описание услуги</label>
|
|
||||||
<textarea id="message" class="form-control" name="message" rows="4" required minlength="10" maxlength="1000"></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Отправить заявку</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<label for="clientEmail">Ваш email:</label>
|
||||||
|
<input type="email" class="form-control" id="clientEmail" name="client_email" placeholder="Введите ваш email" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="clientPhone">Ваш телефон:</label>
|
||||||
|
<input type="text" class="form-control" id="clientPhone" name="client_phone" placeholder="Введите ваш телефон" required pattern="^\+?[0-9\s\-]{7,15}$">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Описание заявки:</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" placeholder="Опишите вашу заявку" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success" id="submitButton" disabled>Отправить заявку</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Обработчик открытия модального окна
|
||||||
|
document.getElementById('openModalBtn').addEventListener('click', function () {
|
||||||
|
const serviceId = this.getAttribute('data-service-id');
|
||||||
|
// Открываем модальное окно
|
||||||
|
document.getElementById('serviceModal').style.display = 'block';
|
||||||
|
|
||||||
|
// Выполняем запрос на генерацию QR-кода
|
||||||
|
fetch(`/service/generate_qr_code/${serviceId}/`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Обновляем src изображения QR-кода
|
||||||
|
document.getElementById('qrCodeImg').src = data.qr_code_url;
|
||||||
|
|
||||||
|
// Запуск проверки статуса каждые 5 секунд
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
checkVerificationStatus(data.service_request_id, interval);
|
||||||
|
}, 5000);
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Ошибка при генерации QR-кода:', error));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик закрытия модального окна
|
||||||
|
document.querySelector('.close').addEventListener('click', function () {
|
||||||
|
document.getElementById('serviceModal').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик отправки формы
|
||||||
|
document.getElementById('serviceRequestForm').addEventListener('submit', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const serviceId = document.getElementById('openModalBtn').getAttribute('data-service-id');
|
||||||
|
// Отправка данных формы на сервер
|
||||||
|
fetch('/service/request/' + serviceId + '/', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
alert('Заявка успешно отправлена!');
|
||||||
|
document.getElementById('serviceModal').style.display = 'none';
|
||||||
|
// Отправка сообщения в Telegram
|
||||||
|
sendTelegramNotification(formData);
|
||||||
|
} else {
|
||||||
|
alert('Ошибка при отправке заявки. Пожалуйста, попробуйте снова.');
|
||||||
|
}
|
||||||
|
}).catch(error => console.error('Ошибка при отправке данных формы:', error));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Проверка статуса заявки на наличие подтверждения Telegram
|
||||||
|
const checkVerificationStatus = (serviceRequestId, interval) => {
|
||||||
|
fetch(`/service/request_status/${serviceRequestId}/`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.is_verified) {
|
||||||
|
// Заполнение полей формы данными пользователя
|
||||||
|
document.getElementById('clientName').value = data.client_name;
|
||||||
|
document.getElementById('clientChatId').value = data.client_chat_id;
|
||||||
|
|
||||||
|
// Активируем кнопку отправки, если все поля заполнены
|
||||||
|
updateButtonState();
|
||||||
|
|
||||||
|
// Останавливаем интервал проверки статуса
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Ошибка при проверке статуса заявки:', error));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Код для активации кнопки "Отправить" при заполнении всех полей
|
||||||
|
const clientEmail = document.getElementById('clientEmail');
|
||||||
|
const clientPhone = document.getElementById('clientPhone');
|
||||||
|
const clientName = document.getElementById('clientName');
|
||||||
|
const description = document.getElementById('description');
|
||||||
|
const clientChatId = document.getElementById('clientChatId');
|
||||||
|
const submitButton = document.getElementById('submitButton');
|
||||||
|
|
||||||
|
const updateButtonState = () => {
|
||||||
|
if (clientEmail.value && clientPhone.value && clientName.value && description.value && clientChatId.value) {
|
||||||
|
submitButton.disabled = false;
|
||||||
|
} else {
|
||||||
|
submitButton.disabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Привязка событий к полям для обновления состояния кнопки отправки
|
||||||
|
clientEmail.addEventListener('input', updateButtonState);
|
||||||
|
clientPhone.addEventListener('input', updateButtonState);
|
||||||
|
description.addEventListener('input', updateButtonState);
|
||||||
|
|
||||||
|
// Удаление placeholder при установке фокуса на поле
|
||||||
|
document.querySelectorAll('input, textarea').forEach(field => {
|
||||||
|
field.addEventListener('focus', function () {
|
||||||
|
this.dataset.placeholder = this.placeholder;
|
||||||
|
this.placeholder = '';
|
||||||
|
});
|
||||||
|
field.addEventListener('blur', function () {
|
||||||
|
this.placeholder = this.dataset.placeholder;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Функция для отправки уведомления в Telegram
|
||||||
|
const sendTelegramNotification = (formData) => {
|
||||||
|
const clientName = formData.get('client_name');
|
||||||
|
const serviceDescription = formData.get('description');
|
||||||
|
const chatId = formData.get('client_chat_id');
|
||||||
|
const message = `Здравствуйте, ${clientName}! Ваша заявка успешно зарегистрирована. Детали: ${serviceDescription}`;
|
||||||
|
|
||||||
|
fetch('/service/send_telegram_notification/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ chat_id: chatId, message: message })
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('Уведомление успешно отправлено в Telegram');
|
||||||
|
} else {
|
||||||
|
console.error('Ошибка при отправке уведомления в Telegram');
|
||||||
|
}
|
||||||
|
}).catch(error => console.error('Ошибка при отправке уведомления в Telegram:', error));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
161
smartsoltech/web/templates/web/modal_order_form_old.html
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<!-- web/templates/web/modal_order_form.html -->
|
||||||
|
<div id="orderModal" class="modal fade" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Оформление заявки на услугу</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="orderForm" method="post" action="{% url 'create_service_request' service_id=service.pk %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label">Имя</label>
|
||||||
|
<input id="client_name" class="form-control" type="text" name="client_name" required minlength="2" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label">Телефон</label>
|
||||||
|
<input id="client_phone" class="form-control" type="tel" name="client_phone" required pattern="^\+?[0-9\s\-]{7,15}$" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label">Адрес электронной почты</label>
|
||||||
|
<input id="client_email" class="form-control" type="email" name="client_email" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3 text-center">
|
||||||
|
<label class="form-label">Сканируйте QR код для регистрации в Telegram боте</label>
|
||||||
|
<div id="qrCodeContainer">
|
||||||
|
<img id="qrCodeImage" src="{{ qr_code_url }}" alt="QR код для Telegram бота" class="img-fluid" style="max-width: 150px;" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<a id="registrationLink" href="#" target="_blank">Перейдите по этой ссылке для регистрации в Telegram боте</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||||
|
<button type="submit" class="btn btn-primary" id="submitButton" disabled>Отправить заявку</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% comment %} <div id="diagnostic_info" class="mt-3">
|
||||||
|
<form id="registrationForm">
|
||||||
|
<h3>Диагностическая информация</h3>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="registrationLinkField" class="form-label">Ссылка для регистрации в Telegram боте:</label>
|
||||||
|
<input type="text" id="registrationLinkField" class="form-control" readonly />
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="requestIdField" class="form-label">Номер заявки:</label>
|
||||||
|
<input type="text" id="requestIdField" class="form-control" readonly />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div> {% endcomment %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const form = document.getElementById('registrationForm');
|
||||||
|
const submitButton = document.getElementById('submitButton');
|
||||||
|
const qrCodeImage = document.getElementById('qrCodeImage');
|
||||||
|
const registrationLink = document.getElementById('registrationLink');
|
||||||
|
let serviceId = "{{ service.pk }}"; // Получаем значение serviceId из шаблона
|
||||||
|
|
||||||
|
// Генерация QR-кода при открытии формы
|
||||||
|
fetch(`/service/generate_qr_code/${serviceId}/`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
qrCodeImage.src = data.qr_code_url;
|
||||||
|
registrationLink.href = data.registration_link;
|
||||||
|
|
||||||
|
// Извлекаем номер заявки из ссылки на регистрацию
|
||||||
|
const requestMatch = data.registration_link.match(/request_(\d+)_token/);
|
||||||
|
if (requestMatch) {
|
||||||
|
serviceId = requestMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отображение диагностической информации на форме
|
||||||
|
//document.getElementById('registrationLinkField').value = data.registration_link;
|
||||||
|
//document.getElementById('requestIdField').value = serviceId;
|
||||||
|
|
||||||
|
// Запуск проверки статуса каждые 5 секунд
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
checkVerificationStatus(serviceId, interval);
|
||||||
|
}, 5000);
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Ошибка при генерации QR-кода:', error));
|
||||||
|
|
||||||
|
const checkVerificationStatus = (serviceId, interval) => {
|
||||||
|
fetch(`/service/request_status/${serviceId}/`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data.is_verified) {
|
||||||
|
// Заполнение полей формы данными пользователя
|
||||||
|
document.getElementById('client_name').value = data.client_name;
|
||||||
|
document.getElementById('client_email').value = data.client_email;
|
||||||
|
document.getElementById('client_phone').value = data.client_phone;
|
||||||
|
|
||||||
|
// Активируем кнопку отправки, если поля заполнены
|
||||||
|
updateButtonState();
|
||||||
|
|
||||||
|
// Останавливаем интервал проверки статуса
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Ошибка при проверке статуса:', error));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчик кнопки отправки формы
|
||||||
|
submitButton.addEventListener('click', function () {
|
||||||
|
if (submitButton.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка наличия данных в таблице ServiceRequest перед отправкой формы
|
||||||
|
fetch(`/service/check_service_request_data/?request_id=${serviceId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.exists) {
|
||||||
|
// Обратная связь пользователю
|
||||||
|
alert('Заявка уже существует. Данные будут обновлены.');
|
||||||
|
|
||||||
|
// Заполнение формы данными из заявки
|
||||||
|
document.getElementById('client_name').value = data.client_name;
|
||||||
|
document.getElementById('client_email').value = data.client_email;
|
||||||
|
document.getElementById('client_phone').value = data.client_phone;
|
||||||
|
|
||||||
|
// Отправка формы
|
||||||
|
form.submit();
|
||||||
|
} else {
|
||||||
|
// Если данных нет, отправляем форму как новую заявку
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Ошибка при проверке данных заявки:', error);
|
||||||
|
alert('Произошла ошибка при проверке данных. Пожалуйста, попробуйте еще раз.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Код для активации кнопки "Отправить" при заполнении полей телефона и email
|
||||||
|
const clientEmail = document.getElementById('client_email');
|
||||||
|
const clientPhone = document.getElementById('client_phone');
|
||||||
|
|
||||||
|
const updateButtonState = () => {
|
||||||
|
if (clientEmail.value && clientPhone.value) {
|
||||||
|
submitButton.disabled = false;
|
||||||
|
} else {
|
||||||
|
submitButton.disabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Привязка событий к полям email и телефона
|
||||||
|
clientEmail.addEventListener('input', updateButtonState);
|
||||||
|
clientPhone.addEventListener('input', updateButtonState);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
14
smartsoltech/web/templates/web/order_detail.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'web/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Детали заказа</h2>
|
||||||
|
|
||||||
|
<p><strong>Номер заказа:</strong> {{ order.id }}</p>
|
||||||
|
<p><strong>Услуга:</strong> {{ order.service.name }}</p>
|
||||||
|
<p><strong>Клиент:</strong> {{ order.client.first_name }} {{ order.client.last_name }}</p>
|
||||||
|
<p><strong>Сообщение:</strong> {{ order.message }}</p>
|
||||||
|
<p><strong>Статус:</strong> {{ order.get_status_display }}</p>
|
||||||
|
<p><strong>Дата создания:</strong> {{ order.order_date }}</p>
|
||||||
|
|
||||||
|
<a href="{% url 'home' %}">Вернуться на главную</a>
|
||||||
|
{% endblock %}
|
||||||
@@ -14,7 +14,11 @@
|
|||||||
<div style="max-width: 350px;">
|
<div style="max-width: 350px;">
|
||||||
<h2 class="text-uppercase fw-bold">{{ service.name }}<br /></h2>
|
<h2 class="text-uppercase fw-bold">{{ service.name }}<br /></h2>
|
||||||
<p class="my-3">{{ service.description }}</p>
|
<p class="my-3">{{ service.description }}</p>
|
||||||
<a class="btn btn-primary btn-lg me-2" role="button" href="#" data-bs-toggle="modal" data-bs-target="#orderModal">Order Service</a>
|
<button id="orderButton" data-service-id="{{ service.pk }}" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#orderModal">Заказать услугу</button>
|
||||||
|
<!-- Кнопка открытия модального окна -->
|
||||||
|
<button id="openModalBtn" class="btn btn-primary" data-service-id="{{ service.id }}">Открыть заявку на услугу</button>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
10
smartsoltech/web/templates/web/service_request_success.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'web/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2>Заявка успешно создана</h2>
|
||||||
|
<p>Спасибо, ваша заявка на услугу "{{ service_request.service.name }}" успешно создана.</p>
|
||||||
|
<p>Вы также можете перейти к своему заказу ниже:</p>
|
||||||
|
<a href="{% url 'order_detail' order.pk %}" class="btn btn-primary">Перейти к заказу</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -11,8 +11,17 @@ urlpatterns = [
|
|||||||
path('client/<int:pk>/', views.client_detail, name='client_detail'),
|
path('client/<int:pk>/', views.client_detail, name='client_detail'),
|
||||||
path('blog/<int:pk>/', views.blog_post_detail, name='blog_post_detail'),
|
path('blog/<int:pk>/', views.blog_post_detail, name='blog_post_detail'),
|
||||||
path('services/', views.services_view, name='services'),
|
path('services/', views.services_view, name='services'),
|
||||||
path('create_order/<int:pk>/', views.create_order, name='create_order'),
|
# path('create_order/<int:pk>/', views.create_order, name='create_order'),
|
||||||
path('about/', views.about_view, name="about_view"),
|
path('about/', views.about_view, name="about_view"),
|
||||||
|
path('service/generate_qr_code/<int:service_id>/', views.generate_qr_code, name='generate_qr_code'),
|
||||||
|
path('service/request_status/<int:service_id>/', views.request_status, name='request_status'),
|
||||||
|
path('service/request/<int:service_id>/', views.create_service_request, name='create_service_request'),
|
||||||
|
path('complete_registration/<int:request_id>/', views.complete_registration, name='complete_registration'),
|
||||||
|
path('complete_registration/', views.complete_registration_basic, name='complete_registration_basic'),
|
||||||
|
path('service/check_service_request_data/', views.check_service_request_data, name='check_service_request_data'),
|
||||||
|
path('client/orders/', views.client_orders, name='client_orders'),
|
||||||
|
path('order/<int:pk>/', views.order_detail, name='order_detail'),
|
||||||
|
path('service/send_telegram_notification/', views.send_telegram_notification, name='send_telegram_notification'),
|
||||||
]
|
]
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
@@ -1,7 +1,31 @@
|
|||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from .models import Service, Project, Client, BlogPost, Review, Order
|
from .models import Service, Project, Client, BlogPost, Review, Order, ServiceRequest
|
||||||
from django.db.models import Avg
|
from django.db.models import Avg
|
||||||
|
from comunication.models import TelegramSettings
|
||||||
|
import qrcode
|
||||||
|
import os
|
||||||
|
from django.conf import settings
|
||||||
|
import uuid
|
||||||
|
from django.utils.http import urlsafe_base64_encode
|
||||||
|
from django.utils.encoding import force_bytes
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.utils.crypto import get_random_string # Импорт get_random_string
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from decouple import config
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from comunication.telegram_bot import TelegramBot
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
# sens
|
||||||
|
try:
|
||||||
|
bot = TelegramBot()
|
||||||
|
except Exception as e:
|
||||||
|
print (e)
|
||||||
|
|
||||||
def home(request):
|
def home(request):
|
||||||
services = Service.objects.all()
|
services = Service.objects.all()
|
||||||
@@ -37,60 +61,236 @@ def services_view(request):
|
|||||||
services = Service.objects.all()
|
services = Service.objects.all()
|
||||||
return render(request, 'web/services.html', {'services': services})
|
return render(request, 'web/services.html', {'services': services})
|
||||||
|
|
||||||
# def create_order(request, pk):
|
def about_view(request):
|
||||||
# if request.method == 'POST':
|
return render(request, 'web/about.html')
|
||||||
# service = get_object_or_404(Service, pk=pk)
|
|
||||||
# client_name = request.POST.get('client_name')
|
|
||||||
# client_email = request.POST.get('client_email')
|
|
||||||
# client_phone = request.POST.get('client_phone')
|
|
||||||
# message = request.POST.get('message')
|
|
||||||
|
|
||||||
# # Создаем клиента, если он не существует
|
def create_service_request(request, service_id):
|
||||||
# client, created = Client.objects.get_or_create(
|
service = get_object_or_404(Service, pk=service_id)
|
||||||
# email=client_email,
|
|
||||||
# defaults={'first_name': client_name, 'phone_number': client_phone}
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Создаем новый заказ
|
|
||||||
# order = Order(
|
|
||||||
# service=service,
|
|
||||||
# client=client,
|
|
||||||
# client_email=client.email,
|
|
||||||
# client_phone=client.phone_number,
|
|
||||||
# message=message,
|
|
||||||
# )
|
|
||||||
# order.save()
|
|
||||||
|
|
||||||
# # Редирект на страницу подтверждения или обратно к услуге
|
|
||||||
# return redirect('service_detail', pk=pk)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_order(request, pk):
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
service = get_object_or_404(Service, pk=pk)
|
|
||||||
client_name = request.POST.get('client_name')
|
client_name = request.POST.get('client_name')
|
||||||
client_email = request.POST.get('client_email')
|
client_email = request.POST.get('client_email')
|
||||||
client_phone = request.POST.get('client_phone')
|
client_phone = request.POST.get('client_phone')
|
||||||
message = request.POST.get('message')
|
description = request.POST.get('description') # New description field
|
||||||
|
chat_id = request.POST.get('chat_id')
|
||||||
|
token = uuid.uuid4().hex
|
||||||
|
|
||||||
# Создаем клиента, если он не существует
|
# Check for existing service request
|
||||||
client, created = Client.objects.get_or_create(
|
service_request = ServiceRequest.objects.filter(client_email=client_email, client_phone=client_phone).first()
|
||||||
email=client_email,
|
|
||||||
defaults={'first_name': client_name, 'phone_number': client_phone}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Создаем новый заказ
|
if not service_request:
|
||||||
order = Order(
|
# Create a new service request
|
||||||
service=service,
|
service_request = ServiceRequest.objects.create(
|
||||||
client=client,
|
service=service,
|
||||||
message=message,
|
client_name=client_name,
|
||||||
)
|
client_email=client_email,
|
||||||
order.save()
|
client_phone=client_phone,
|
||||||
|
chat_id=chat_id,
|
||||||
|
token=token
|
||||||
|
)
|
||||||
|
|
||||||
# Редирект на страницу подтверждения или обратно к услуге
|
# Generate user credentials
|
||||||
return redirect('service_detail', pk=pk)
|
username = f"{client_email.split('@')[0]}_{get_random_string(5)}"
|
||||||
|
password = get_random_string(8)
|
||||||
|
|
||||||
def about_view(request):
|
# Create a new user
|
||||||
return render(request, 'web/about.html')
|
user = User.objects.create_user(username=username, password=password)
|
||||||
|
user.first_name = client_name.split()[0] if client_name else ""
|
||||||
|
user.last_name = client_name.split()[-1] if len(client_name.split()) > 1 else ""
|
||||||
|
user.email = client_email
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
# Create or get client
|
||||||
|
client, created = Client.objects.get_or_create(
|
||||||
|
email=client_email,
|
||||||
|
defaults={
|
||||||
|
'first_name': user.first_name,
|
||||||
|
'last_name': user.last_name,
|
||||||
|
'phone_number': client_phone,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an order linked to the service request and client
|
||||||
|
order = Order.objects.create(
|
||||||
|
service=service,
|
||||||
|
client=client,
|
||||||
|
message=description,
|
||||||
|
status="pending"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send credentials via Telegram
|
||||||
|
if chat_id:
|
||||||
|
bot.send_telegram_message(client.id, service_request.id, "Ваши данные для входа на сайт.", order.id)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If service request exists, update the chat_id
|
||||||
|
service_request.chat_id = chat_id
|
||||||
|
service_request.save()
|
||||||
|
|
||||||
|
return redirect(reverse('order_detail', args=[order.pk]))
|
||||||
|
|
||||||
|
def generate_qr_code(request, service_id):
|
||||||
|
service = get_object_or_404(Service, pk=service_id)
|
||||||
|
telegram_settings = get_object_or_404(TelegramSettings, pk=1)
|
||||||
|
token = uuid.uuid4().hex
|
||||||
|
|
||||||
|
# Создание новой заявки на услугу
|
||||||
|
service_request = ServiceRequest.objects.create(
|
||||||
|
service=service,
|
||||||
|
client_name='',
|
||||||
|
client_email='',
|
||||||
|
client_phone='',
|
||||||
|
token=token
|
||||||
|
)
|
||||||
|
|
||||||
|
# Генерация ссылки для регистрации в Telegram
|
||||||
|
registration_link = (f'https://t.me/{telegram_settings.bot_name}?start=request_{service_request.id}_token_{urlsafe_base64_encode(force_bytes(token))}'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Генерация QR-кода
|
||||||
|
qr = qrcode.make(registration_link)
|
||||||
|
qr_code_dir = os.path.join(settings.STATICFILES_DIRS[0], 'qr_codes')
|
||||||
|
qr_code_path = os.path.join(qr_code_dir, f"request_{service_request.id}.png")
|
||||||
|
external_qr_link = f'static/qr_codes/request_{service_request.id}.png'
|
||||||
|
|
||||||
|
if not os.path.exists(qr_code_dir):
|
||||||
|
os.makedirs(qr_code_dir)
|
||||||
|
|
||||||
|
qr.save(qr_code_path)
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'registration_link': registration_link,
|
||||||
|
'qr_code_url': f"/{external_qr_link}",
|
||||||
|
'service_request_id': service_request.id
|
||||||
|
})
|
||||||
|
|
||||||
|
def complete_registration(request, request_id):
|
||||||
|
# Завершение регистрации по идентификатору заявки
|
||||||
|
service_request = get_object_or_404(ServiceRequest, pk=request_id)
|
||||||
|
if request.method == 'POST':
|
||||||
|
client_name = request.POST.get('client_name', service_request.client_name)
|
||||||
|
client_email = request.POST.get('client_email', service_request.client_email)
|
||||||
|
client_phone = request.POST.get('client_phone', service_request.client_phone)
|
||||||
|
chat_id = request.POST.get('chat_id', service_request.chat_id)
|
||||||
|
|
||||||
|
# Обновляем данные заявки
|
||||||
|
service_request.client_name = client_name
|
||||||
|
service_request.client_email = client_email
|
||||||
|
service_request.client_phone = client_phone
|
||||||
|
service_request.chat_id = chat_id
|
||||||
|
service_request.save()
|
||||||
|
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
return render(request, 'web/complete_registration.html', {'service_request': service_request})
|
||||||
|
def request_status(request, service_id):
|
||||||
|
# Проверяем статус заявки на услугу
|
||||||
|
service_request = get_object_or_404(ServiceRequest, pk=service_id)
|
||||||
|
is_verified = service_request.client_name != '' and service_request.chat_id != ''
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'is_verified': is_verified,
|
||||||
|
'client_name': service_request.client_name,
|
||||||
|
'client_chat_id' : service_request.chat_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
def complete_registration_basic(request):
|
||||||
|
# Базовая регистрация без идентификатора заявки
|
||||||
|
if request.method == 'POST':
|
||||||
|
client_name = request.POST.get('client_name')
|
||||||
|
client_email = request.POST.get('client_email')
|
||||||
|
client_phone = request.POST.get('client_phone')
|
||||||
|
|
||||||
|
# # Создаем новую запись заявки
|
||||||
|
# service_request = ServiceRequest.objects.create(
|
||||||
|
# client_name=client_name,
|
||||||
|
# client_email=client_email,
|
||||||
|
# client_phone=client_phone
|
||||||
|
# )
|
||||||
|
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
return render(request, 'web/complete_registration_basic.html')
|
||||||
|
|
||||||
|
def check_service_request_data(request, token=None, request_id=None):
|
||||||
|
# Проверка наличия данных в таблице ServiceRequest по токену или номеру заявки
|
||||||
|
service_request = None
|
||||||
|
if token:
|
||||||
|
try:
|
||||||
|
service_request = ServiceRequest.objects.get(token=token)
|
||||||
|
except ServiceRequest.DoesNotExist:
|
||||||
|
service_request = None
|
||||||
|
elif request_id:
|
||||||
|
try:
|
||||||
|
service_request = ServiceRequest.objects.get(id=request_id)
|
||||||
|
except ServiceRequest.DoesNotExist:
|
||||||
|
service_request = None
|
||||||
|
|
||||||
|
if service_request:
|
||||||
|
return JsonResponse({
|
||||||
|
'exists': True,
|
||||||
|
'client_name': service_request.client_name,
|
||||||
|
'client_email': service_request.client_email,
|
||||||
|
'client_phone': service_request.client_phone,
|
||||||
|
'chat_id': service_request.chat_id
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'exists': False})
|
||||||
|
|
||||||
|
def order_detail(request, pk):
|
||||||
|
order = get_object_or_404(Order, pk=pk)
|
||||||
|
return render(request, 'web/order_detail.html', {'order': order})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def client_orders(request):
|
||||||
|
client = request.user.client_profile
|
||||||
|
orders = client.related_orders.all()
|
||||||
|
return render(request, 'web/client_orders.html', {'orders': orders})
|
||||||
|
|
||||||
|
|
||||||
|
def generate_secure_token(service_request_id, secret_key):
|
||||||
|
"""Генерация безопасного токена для подтверждения подлинности запроса."""
|
||||||
|
data = f'{service_request_id}:{secret_key}'
|
||||||
|
return hmac.new(secret_key.encode(), data.encode(), hashlib.sha256).hexdigest()
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def send_telegram_notification(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
service_request_id = data.get('service_request_id')
|
||||||
|
provided_token = data.get('token')
|
||||||
|
|
||||||
|
# Проверка корректности переданных данных
|
||||||
|
if not service_request_id or not provided_token:
|
||||||
|
return JsonResponse({'error': 'Недостаточно данных для подтверждения'}, status=400)
|
||||||
|
|
||||||
|
# Получение заявки
|
||||||
|
service_request = ServiceRequest.objects.filter(id=service_request_id).first()
|
||||||
|
if not service_request:
|
||||||
|
return JsonResponse({'error': 'Заявка не найдена'}, status=404)
|
||||||
|
|
||||||
|
# Генерация токена и сравнение
|
||||||
|
secret_key = settings.SECRET_KEY # Используем секретный ключ из настроек
|
||||||
|
expected_token = generate_secure_token(service_request_id, secret_key)
|
||||||
|
|
||||||
|
if not hmac.compare_digest(provided_token, expected_token):
|
||||||
|
return JsonResponse({'error': 'Неверный токен. Доступ запрещен.'}, status=403)
|
||||||
|
|
||||||
|
# Отправка сообщения в Telegram
|
||||||
|
chat_id = service_request.chat_id
|
||||||
|
if not chat_id:
|
||||||
|
return JsonResponse({'error': 'Нет chat_id для отправки сообщения'}, status=400)
|
||||||
|
|
||||||
|
message = (
|
||||||
|
f"Здравствуйте, {service_request.client_name}!\n"
|
||||||
|
f"Ваша заявка на услугу '{service_request.service.name}' успешно зарегистрирована."
|
||||||
|
)
|
||||||
|
|
||||||
|
bot.send_telegram_message(chat_id=chat_id, message=message)
|
||||||
|
|
||||||
|
return JsonResponse({'status': 'Уведомление успешно отправлено в Telegram'})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'error': str(e)}, status=500)
|
||||||
|
|
||||||
|
return JsonResponse({'error': 'Метод запроса должен быть POST'}, status=405)
|
||||||