v1.6.0: max_bot fixes — feature keys, flush→commit, test-run, categories, broadcast page, proxy error handling, deploy scripts
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
from datetime import datetime
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Boolean, Text, DateTime, BigInteger, SmallInteger, JSON, ForeignKey, CheckConstraint, text as sa_text
|
||||
)
|
||||
from sqlalchemy.orm import relationship, declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class BotUser(Base):
|
||||
__tablename__ = "bot_users"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
max_user_id = Column(BigInteger, unique=True, nullable=False)
|
||||
first_name = Column(String(255), default="")
|
||||
last_name = Column(String(255), default="")
|
||||
username = Column(String(255), default="")
|
||||
phone = Column(String(50), default="")
|
||||
email = Column(String(255), default="")
|
||||
consent_given = Column(Boolean, nullable=False, default=False)
|
||||
consent_timestamp = Column(DateTime, nullable=True)
|
||||
consent_method = Column(String(50), default="")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
last_active_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
|
||||
conversations = relationship("BotConversation", back_populates="user")
|
||||
consent_logs = relationship("BotConsentLog", back_populates="user")
|
||||
notification_subscriptions = relationship("BotNotificationSubscription", back_populates="user", uselist=False)
|
||||
|
||||
|
||||
class BotConversation(Base):
|
||||
__tablename__ = "bot_conversations"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("bot_users.id", ondelete="CASCADE"), nullable=False)
|
||||
status = Column(String(20), nullable=False, default="open")
|
||||
inquiry_type = Column(Integer, ForeignKey("bot_categories.id", ondelete="SET NULL"), nullable=True)
|
||||
inquiry_text = Column(Text, default="")
|
||||
priority = Column(String(20), default="normal")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
closed_at = Column(DateTime, nullable=True)
|
||||
assigned_to = Column(String(255), default="")
|
||||
has_attachment = Column(Boolean, default=False)
|
||||
attachment_type = Column(String(50), default="")
|
||||
attachment_path = Column(String(500), default="")
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('open','closed','handoff','1c_sent','awaiting_rating')", name="ck_bot_conv_status"),
|
||||
CheckConstraint("priority IN ('low','normal','high','urgent')", name="ck_bot_conv_priority"),
|
||||
)
|
||||
|
||||
user = relationship("BotUser", back_populates="conversations")
|
||||
category = relationship("BotCategory", back_populates="conversations")
|
||||
messages = relationship("BotMessage", back_populates="conversation", order_by="BotMessage.timestamp")
|
||||
sync_log = relationship("Bot1CSyncLog", back_populates="conversation")
|
||||
|
||||
|
||||
class BotMessage(Base):
|
||||
__tablename__ = "bot_messages"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
conversation_id = Column(Integer, ForeignKey("bot_conversations.id", ondelete="CASCADE"), nullable=False)
|
||||
direction = Column(String(3), nullable=False)
|
||||
text = Column(Text, default="")
|
||||
has_attachment = Column(Boolean, default=False)
|
||||
attachment_type = Column(String(50), default="")
|
||||
attachment_path = Column(String(500), default="")
|
||||
max_message_id = Column(BigInteger, nullable=True)
|
||||
timestamp = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("direction IN ('in','out')", name="ck_bot_msg_direction"),
|
||||
)
|
||||
|
||||
conversation = relationship("BotConversation", back_populates="messages")
|
||||
|
||||
|
||||
class BotConsentLog(Base):
|
||||
__tablename__ = "bot_consent_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("bot_users.id", ondelete="CASCADE"), nullable=False)
|
||||
action = Column(String(20), nullable=False)
|
||||
timestamp = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
method = Column(String(50), default="")
|
||||
ip_address = Column(String(45), default="")
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("action IN ('given','revoked')", name="ck_bot_consent_action"),
|
||||
)
|
||||
|
||||
user = relationship("BotUser", back_populates="consent_logs")
|
||||
|
||||
|
||||
class Bot1CSyncLog(Base):
|
||||
__tablename__ = "bot_1c_sync_log"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
conversation_id = Column(Integer, ForeignKey("bot_conversations.id", ondelete="CASCADE"), nullable=False)
|
||||
status = Column(String(20), nullable=False, default="pending")
|
||||
request_payload = Column(Text, default="")
|
||||
response_payload = Column(Text, default="")
|
||||
sent_at = Column(DateTime, nullable=True)
|
||||
confirmed_at = Column(DateTime, nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('pending','sent','confirmed','error')", name="ck_bot_1c_status"),
|
||||
)
|
||||
|
||||
conversation = relationship("BotConversation", back_populates="sync_log")
|
||||
|
||||
|
||||
class BotKnowledgeBase(Base):
|
||||
__tablename__ = "bot_knowledge_base"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
question = Column(String(500), nullable=False)
|
||||
answer = Column(Text, nullable=False)
|
||||
category = Column(String(100), default="")
|
||||
keywords = Column(JSON, default=list)
|
||||
active = Column(Boolean, nullable=False, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class BotCategory(Base):
|
||||
__tablename__ = "bot_categories"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
description = Column(String(500), default="")
|
||||
active = Column(Boolean, nullable=False, default=True)
|
||||
sort_order = Column(Integer, default=0)
|
||||
icon_emoji = Column(String(10), default="📋")
|
||||
keywords = Column(JSON, default=list)
|
||||
|
||||
conversations = relationship("BotConversation", back_populates="category")
|
||||
|
||||
|
||||
class BotSetting(Base):
|
||||
__tablename__ = "bot_settings"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
key = Column(String(100), unique=True, nullable=False)
|
||||
value = Column(Text, default="")
|
||||
description = Column(String(500), default="")
|
||||
|
||||
|
||||
class BotFeature(Base):
|
||||
__tablename__ = "bot_features"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
feature_key = Column(String(100), unique=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
description = Column(String(500), default="")
|
||||
enabled = Column(Boolean, nullable=False, default=False)
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
|
||||
class BotResponseTemplate(Base):
|
||||
__tablename__ = "bot_response_templates"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
template_key = Column(String(100), unique=True, nullable=False)
|
||||
template_text = Column(Text, nullable=False)
|
||||
description = Column(String(500), default="")
|
||||
variables = Column(JSON, default=list)
|
||||
|
||||
|
||||
class FileSyncQueue(Base):
|
||||
__tablename__ = "file_sync_queue"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
local_path = Column(String(500), nullable=False)
|
||||
remote_path = Column(String(500), default="")
|
||||
entity_type = Column(String(50), nullable=False)
|
||||
entity_id = Column(Integer, nullable=False)
|
||||
status = Column(String(20), nullable=False, default="pending")
|
||||
error = Column(Text, default="")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('pending','uploading','synced','error')", name="ck_file_sync_status"),
|
||||
)
|
||||
|
||||
|
||||
class BotBroadcast(Base):
|
||||
__tablename__ = "bot_broadcasts"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
text = Column(Text, nullable=False)
|
||||
sent_at = Column(DateTime, nullable=True)
|
||||
sent_by = Column(Integer, nullable=True)
|
||||
recipient_count = Column(Integer, default=0)
|
||||
recipient_ids = Column(JSON, default=list)
|
||||
status = Column(String(20), default="draft")
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('draft','sent','failed')", name="ck_bot_broadcast_status"),
|
||||
)
|
||||
|
||||
|
||||
class BotNotification(Base):
|
||||
__tablename__ = "bot_notifications"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
type = Column(String(50), nullable=False)
|
||||
recipient_type = Column(String(20), nullable=False)
|
||||
recipient_max_user_id = Column(BigInteger, nullable=True)
|
||||
title = Column(String(255), default="")
|
||||
body = Column(Text, default="")
|
||||
related_type = Column(String(50), default="")
|
||||
related_id = Column(Integer, nullable=True)
|
||||
is_read = Column(Boolean, nullable=False, default=False)
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("recipient_type IN ('operator','level2','client')", name="ck_bot_notif_recipient"),
|
||||
)
|
||||
|
||||
|
||||
class BotNotificationSubscription(Base):
|
||||
__tablename__ = "bot_notification_subscriptions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("bot_users.id", ondelete="CASCADE"), nullable=False)
|
||||
notify_on_status_change = Column(Boolean, nullable=False, default=False)
|
||||
notify_on_reply = Column(Boolean, nullable=False, default=False)
|
||||
notify_on_broadcast = Column(Boolean, nullable=False, default=False)
|
||||
|
||||
user = relationship("BotUser", back_populates="notification_subscriptions")
|
||||
|
||||
|
||||
class Customer(Base):
|
||||
__tablename__ = "customers"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
contact_phone = Column(String(50), default="")
|
||||
|
||||
|
||||
class Object(Base):
|
||||
__tablename__ = "objects"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id", ondelete="SET NULL"), nullable=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
status = Column(String(20), nullable=False, default="active")
|
||||
risk_score = Column(Integer, default=0)
|
||||
|
||||
|
||||
class SLAContract(Base):
|
||||
__tablename__ = "sla_contracts"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
object_id = Column(Integer, ForeignKey("objects.id", ondelete="CASCADE"), nullable=False)
|
||||
status = Column(String(20), nullable=False, default="negotiation")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
service_level = Column(String(20), default="start")
|
||||
sla_price_monthly = Column(Integer, default=0)
|
||||
|
||||
|
||||
class Task(Base):
|
||||
__tablename__ = "tasks"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
object_id = Column(Integer, ForeignKey("objects.id", ondelete="CASCADE"), nullable=False)
|
||||
status = Column(String(20), nullable=False, default="open")
|
||||
|
||||
|
||||
class Incident(Base):
|
||||
__tablename__ = "incidents"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
object_id = Column(Integer, ForeignKey("objects.id", ondelete="CASCADE"), nullable=False)
|
||||
status = Column(String(20), nullable=False, default="open")
|
||||
created_at = Column(DateTime, nullable=False, server_default="NOW()")
|
||||
title = Column(String(500), default="")
|
||||
description = Column(Text, default="")
|
||||
severity = Column(String(4), nullable=False, default="P3")
|
||||
reported_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
login = Column(String(50), default="")
|
||||
full_name = Column(String(255), default="")
|
||||
role = Column(String(20), nullable=False, default="technician")
|
||||
is_active = Column(Boolean, nullable=False, default=True)
|
||||
max_user_id = Column(BigInteger, nullable=True)
|
||||
|
||||
|
||||
class BotRiskQuestion(Base):
|
||||
__tablename__ = "bot_risk_questions"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
question_key = Column(String(100), unique=True, nullable=False)
|
||||
question = Column(String(500), nullable=False)
|
||||
weight = Column(Integer, nullable=False, default=10)
|
||||
sort_order = Column(Integer, default=0)
|
||||
is_active = Column(Boolean, nullable=False, default=True)
|
||||
|
||||
|
||||
class BotTicket(Base):
|
||||
__tablename__ = "bot_tickets"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
ticket_number = Column(String(20), unique=True, nullable=False)
|
||||
user_id = Column(Integer, ForeignKey("bot_users.id", ondelete="SET NULL"), nullable=True)
|
||||
object_id = Column(Integer, nullable=True)
|
||||
customer_id = Column(Integer, nullable=True)
|
||||
title = Column(String(255), nullable=False)
|
||||
description = Column(Text, default="")
|
||||
priority = Column(String(20), default="normal")
|
||||
status = Column(String(20), nullable=False, default="open")
|
||||
created_by = Column(String(20), default="bot")
|
||||
sla_contract_id = Column(Integer, nullable=True)
|
||||
has_attachment = Column(Boolean, default=False)
|
||||
attachment_type = Column(String(50), default="")
|
||||
attachment_path = Column(String(500), default="")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
updated_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("priority IN ('low','normal','high','urgent')", name="ck_bot_ticket_priority"),
|
||||
CheckConstraint("status IN ('open','in_progress','resolved','closed')", name="ck_bot_ticket_status"),
|
||||
)
|
||||
|
||||
|
||||
class BotTicketMessage(Base):
|
||||
__tablename__ = "bot_ticket_messages"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
ticket_id = Column(Integer, ForeignKey("bot_tickets.id", ondelete="CASCADE"), nullable=False)
|
||||
direction = Column(String(10), nullable=False)
|
||||
text = Column(Text, default="")
|
||||
sender = Column(String(50), default="")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
|
||||
|
||||
class BotTicketStatus(Base):
|
||||
__tablename__ = "bot_ticket_statuses"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
ticket_id = Column(Integer, ForeignKey("bot_tickets.id", ondelete="CASCADE"), nullable=False)
|
||||
old_status = Column(String(20), default="")
|
||||
new_status = Column(String(20), nullable=False)
|
||||
changed_by = Column(String(50), default="")
|
||||
created_at = Column(DateTime, nullable=False, server_default=sa_text("CURRENT_TIMESTAMP"))
|
||||
Reference in New Issue
Block a user