Shelter PMS fully functional

This commit is contained in:
2024-12-09 16:36:11 +09:00
parent 60eaef5527
commit e76a80fb2f
47 changed files with 665 additions and 909 deletions

View File

@@ -1,5 +1,5 @@
from django.contrib import admin
from django import forms
from django.contrib import admin
from .models import (
Hotel,
UserHotel,
@@ -8,6 +8,10 @@ from .models import (
Reservation,
Guest
)
from django.urls import path
from django.shortcuts import redirect
from django.utils.html import format_html
from pms_integration.api_client import APIClient
# Custom form for Hotel to filter APIConfiguration
class HotelForm(forms.ModelForm):
@@ -22,22 +26,40 @@ class HotelForm(forms.ModelForm):
self.fields['api'].queryset = APIConfiguration.objects.exclude(id__in=used_apis)
@admin.register(Hotel)
class HotelAdmin(admin.ModelAdmin):
form = HotelForm
list_display = ('name', 'api', 'created_at', 'pms')
search_fields = ('name',)
list_filter = ('pms', 'created_at')
ordering = ('-created_at',)
list_display = ['name', 'pms', 'sync_button']
admin.site.register(Hotel, HotelAdmin)
def sync_button(self, obj):
return format_html(
'<a class="button" href="{}">Синхронизировать</a>',
f"/admin/hotels/sync/{obj.id}/"
)
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('sync/<int:hotel_id>/', self.sync_hotel_data),
]
return custom_urls + urls
def sync_hotel_data(self, request, hotel_id):
try:
hotel = Hotel.objects.get(id=hotel_id)
client = APIClient(hotel.pms)
client.run(hotel)
self.message_user(request, f"Данные отеля {hotel.name} успешно синхронизированы.")
except Exception as e:
self.message_user(request, f"Ошибка: {str(e)}", level="error")
return redirect("..")
@admin.register(UserHotel)
class UserHotelAdmin(admin.ModelAdmin):
list_display = ('user', 'hotel')
search_fields = ('user__username', 'hotel__name')
list_filter = ('hotel',)
ordering = ('-hotel',)
# list_filter = ('hotel',)
# ordering = ('-hotel',)
@admin.register(APIConfiguration)

View File

@@ -1,33 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-05 23:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Hotel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Hotel Name')),
('pms_type', models.CharField(choices=[('bnovo', 'Bnovo'), ('travelline', 'Travel Line')], max_length=50, verbose_name='PMS Type')),
('api_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='API Key')),
('public_key', models.CharField(blank=True, max_length=255, null=True, verbose_name='Public Key')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
],
),
migrations.CreateModel(
name='UserHotel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Hotel')),
],
),
]

View File

@@ -1,22 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-05 23:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('hotels', '0001_initial'),
('users', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='User'),
),
]

View File

@@ -1,58 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 04:26
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0002_initial'),
('users', '0004_alter_user_options_alter_userconfirmation_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='hotel',
options={'verbose_name': 'Отель', 'verbose_name_plural': 'Отели'},
),
migrations.AlterModelOptions(
name='userhotel',
options={'verbose_name': 'Пользователь отеля', 'verbose_name_plural': 'Пользователи отелей'},
),
migrations.AlterField(
model_name='hotel',
name='api_key',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='API ключ'),
),
migrations.AlterField(
model_name='hotel',
name='created_at',
field=models.DateTimeField(auto_now_add=True, verbose_name='Создан'),
),
migrations.AlterField(
model_name='hotel',
name='name',
field=models.CharField(max_length=255, verbose_name='Название отеля'),
),
migrations.AlterField(
model_name='hotel',
name='pms_type',
field=models.CharField(choices=[('bnovo', 'Bnovo'), ('travelline', 'Travel Line')], max_length=50, verbose_name='PMS система'),
),
migrations.AlterField(
model_name='hotel',
name='public_key',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Публичный ключ'),
),
migrations.AlterField(
model_name='userhotel',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель'),
),
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,26 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 13:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0003_alter_hotel_options_alter_userhotel_options_and_more'),
]
operations = [
migrations.CreateModel(
name='DataLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('data', models.JSONField(verbose_name='Данные')),
],
options={
'verbose_name': 'Лог данных',
'verbose_name_plural': 'Логи данных',
},
),
]

View File

@@ -1,47 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 13:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0004_datalog'),
]
operations = [
migrations.CreateModel(
name='APIConfiguration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название API')),
('url', models.URLField(verbose_name='URL API')),
('token', models.CharField(blank=True, max_length=255, null=True, verbose_name='Токен')),
('username', models.CharField(blank=True, max_length=255, null=True, verbose_name='Логин')),
('password', models.CharField(blank=True, max_length=255, null=True, verbose_name='Пароль')),
('last_updated', models.DateTimeField(auto_now=True, verbose_name='Дата последнего обновления')),
],
options={
'verbose_name': 'Конфигурация API',
'verbose_name_plural': 'Конфигурации API',
},
),
migrations.CreateModel(
name='APIRequestLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('request_time', models.DateTimeField(auto_now_add=True, verbose_name='Время запроса')),
('response_status', models.IntegerField(verbose_name='HTTP статус ответа')),
('response_data', models.JSONField(blank=True, null=True, verbose_name='Данные ответа')),
('api', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.apiconfiguration', verbose_name='API')),
],
options={
'verbose_name': 'Журнал запросов API',
'verbose_name_plural': 'Журналы запросов API',
},
),
migrations.DeleteModel(
name='DataLog',
),
]

View File

@@ -1,45 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 14:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0005_apiconfiguration_apirequestlog_delete_datalog'),
]
operations = [
migrations.CreateModel(
name='PMSConfiguration',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Название PMS')),
('parser_settings', models.JSONField(default=dict, verbose_name='Настройки разбора данных')),
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
],
options={
'verbose_name': 'PMS система',
'verbose_name_plural': 'PMS системы',
},
),
migrations.RemoveField(
model_name='hotel',
name='api_key',
),
migrations.RemoveField(
model_name='hotel',
name='pms_type',
),
migrations.RemoveField(
model_name='hotel',
name='public_key',
),
migrations.AddField(
model_name='hotel',
name='api',
field=models.OneToOneField(blank=True, help_text='API, связанный с этим отелем.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.apiconfiguration', verbose_name='API'),
),
]

View File

@@ -1,28 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 23:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0006_pmsconfiguration_remove_hotel_api_key_and_more'),
]
operations = [
migrations.CreateModel(
name='PMSIntegrationLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('checked_at', models.DateTimeField(auto_now_add=True, verbose_name='Время проверки')),
('status', models.CharField(choices=[('success', 'Успех'), ('error', 'Ошибка')], max_length=50, verbose_name='Статус')),
('message', models.TextField(blank=True, null=True, verbose_name='Сообщение')),
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
],
options={
'verbose_name': 'Журнал интеграции PMS',
'verbose_name_plural': 'Журналы интеграции PMS',
},
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 23:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0007_pmsintegrationlog'),
]
operations = [
migrations.AddField(
model_name='hotel',
name='pms',
field=models.OneToOneField(blank=True, help_text='PMS система? используемая в заведении', null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.pmsconfiguration', verbose_name='PMS система'),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-06 23:57
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0008_hotel_pms'),
]
operations = [
migrations.AlterField(
model_name='hotel',
name='pms',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hotels.pmsconfiguration', verbose_name='PMS система'),
),
]

View File

@@ -1,33 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 00:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0009_alter_hotel_pms'),
]
operations = [
migrations.AddIndex(
model_name='apirequestlog',
index=models.Index(fields=['api'], name='hotels_apir_api_id_686bb0_idx'),
),
migrations.AddIndex(
model_name='apirequestlog',
index=models.Index(fields=['request_time'], name='hotels_apir_request_f65147_idx'),
),
migrations.AddIndex(
model_name='pmsintegrationlog',
index=models.Index(fields=['hotel'], name='hotels_pmsi_hotel_i_718b32_idx'),
),
migrations.AddIndex(
model_name='pmsintegrationlog',
index=models.Index(fields=['checked_at'], name='hotels_pmsi_checked_e7768d_idx'),
),
migrations.AddIndex(
model_name='pmsintegrationlog',
index=models.Index(fields=['status'], name='hotels_pmsi_status_dbf1d8_idx'),
),
]

View File

@@ -1,48 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 00:35
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0010_apirequestlog_hotels_apir_api_id_686bb0_idx_and_more'),
]
operations = [
migrations.CreateModel(
name='Reservation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('reservation_id', models.BigIntegerField(unique=True, verbose_name='ID бронирования')),
('room_number', models.CharField(max_length=50, verbose_name='Номер комнаты')),
('room_type', models.CharField(max_length=255, verbose_name='Тип комнаты')),
('check_in', models.DateTimeField(verbose_name='Дата заезда')),
('check_out', models.DateTimeField(verbose_name='Дата выезда')),
('status', models.CharField(max_length=50, verbose_name='Статус')),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Цена')),
('discount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Скидка')),
('hotel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hotels.hotel', verbose_name='Отель')),
],
options={
'verbose_name': 'Бронирование',
'verbose_name_plural': 'Бронирования',
},
),
migrations.CreateModel(
name='Guest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Имя гостя')),
('birthdate', models.DateField(blank=True, null=True, verbose_name='Дата рождения')),
('phone', models.CharField(blank=True, max_length=50, null=True, verbose_name='Телефон')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
('reservation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guests', to='hotels.reservation', verbose_name='Бронирование')),
],
options={
'verbose_name': 'Гость',
'verbose_name_plural': 'Гости',
},
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 06:28
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0011_reservation_guest'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AddField(
model_name='userhotel',
name='role',
field=models.CharField(choices=[('admin', 'Admin'), ('manager', 'Manager')], default='manager', max_length=50, verbose_name='Роль'),
),
migrations.AlterField(
model_name='userhotel',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hotel_users', to='hotels.hotel', verbose_name='Отель'),
),
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,24 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 07:04
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0012_userhotel_role_alter_userhotel_hotel_and_more'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.RemoveField(
model_name='userhotel',
name='role',
),
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 08:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0013_remove_userhotel_role_alter_userhotel_user'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 08:29
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0014_alter_userhotel_user'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='userhotels', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 08:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0015_alter_userhotel_user'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,25 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 08:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0016_alter_userhotel_user'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AlterField(
model_name='userhotel',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hotelusers', to='hotels.hotel', verbose_name='Отель'),
),
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='userhotel', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,25 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 08:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0017_alter_userhotel_hotel_alter_userhotel_user'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AlterField(
model_name='userhotel',
name='hotel',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='hotel_users', to='hotels.hotel', verbose_name='Отель'),
),
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotel', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,19 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 11:11
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0018_alter_userhotel_hotel_alter_userhotel_user'),
]
operations = [
migrations.AlterField(
model_name='apirequestlog',
name='response_status',
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(100), django.core.validators.MaxValueValidator(599)], verbose_name='HTTP статус ответа'),
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-07 14:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0019_alter_apirequestlog_response_status'),
('users', '0005_notificationsettings'),
]
operations = [
migrations.AlterField(
model_name='userhotel',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_hotels', to='users.user', verbose_name='Пользователь'),
),
]

View File

@@ -1,30 +0,0 @@
# Generated by Django 5.1.4 on 2024-12-08 23:48
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hotels', '0020_alter_userhotel_user'),
('pms_integration', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='hotel',
name='pms',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pms_integration.pmsconfiguration', verbose_name='PMS система'),
),
migrations.RemoveField(
model_name='pmsintegrationlog',
name='hotel',
),
migrations.DeleteModel(
name='PMSConfiguration',
),
migrations.DeleteModel(
name='PMSIntegrationLog',
),
]

View File

@@ -30,7 +30,7 @@ class Hotel(models.Model):
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Создан")
pms = models.ForeignKey(
'pms_integration.PMSConfiguration', # Используем строковую ссылку
'pms_integration.PMSConfiguration',
on_delete=models.SET_NULL,
null=True,
blank=True,