zammad working

This commit is contained in:
2025-08-20 20:39:20 +02:00
parent d3440ccb1b
commit be581b28f8
32 changed files with 4201 additions and 714 deletions

View File

@@ -0,0 +1,241 @@
"""
Database models for Zammad Integration Module
"""
from datetime import datetime
from typing import Optional, Dict, Any
from enum import Enum
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, JSON, ForeignKey, Index
from sqlalchemy.orm import relationship
from app.db.database import Base
class TicketState(str, Enum):
"""Zammad ticket state enumeration"""
NEW = "new"
OPEN = "open"
PENDING_REMINDER = "pending reminder"
PENDING_CLOSE = "pending close"
CLOSED = "closed"
MERGED = "merged"
REMOVED = "removed"
class ProcessingStatus(str, Enum):
"""Ticket processing status"""
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
SKIPPED = "skipped"
class ZammadTicket(Base):
"""Model for tracking Zammad tickets and their processing status"""
__tablename__ = "zammad_tickets"
# Primary key
id = Column(Integer, primary_key=True, index=True)
# Zammad ticket information
zammad_ticket_id = Column(Integer, unique=True, index=True, nullable=False)
ticket_number = Column(String, index=True, nullable=False)
title = Column(String, nullable=False)
state = Column(String, nullable=False) # Zammad state
priority = Column(String, nullable=True)
customer_email = Column(String, nullable=True)
# Processing information
processing_status = Column(String, default=ProcessingStatus.PENDING.value, nullable=False)
processed_at = Column(DateTime, nullable=True)
processed_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
chatbot_id = Column(String, nullable=True)
# Summary and context
summary = Column(Text, nullable=True)
context_data = Column(JSON, nullable=True) # Original ticket data
error_message = Column(Text, nullable=True)
# Metadata
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Zammad specific metadata
zammad_created_at = Column(DateTime, nullable=True)
zammad_updated_at = Column(DateTime, nullable=True)
zammad_article_count = Column(Integer, default=0, nullable=False)
# Processing configuration snapshot
config_snapshot = Column(JSON, nullable=True) # Config used during processing
# Relationships
processed_by = relationship("User", foreign_keys=[processed_by_user_id])
# Indexes for better query performance
__table_args__ = (
Index("idx_zammad_tickets_status_created", "processing_status", "created_at"),
Index("idx_zammad_tickets_state_processed", "state", "processed_at"),
Index("idx_zammad_tickets_user_status", "processed_by_user_id", "processing_status"),
)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for API responses"""
return {
"id": self.id,
"zammad_ticket_id": self.zammad_ticket_id,
"ticket_number": self.ticket_number,
"title": self.title,
"state": self.state,
"priority": self.priority,
"customer_email": self.customer_email,
"processing_status": self.processing_status,
"processed_at": self.processed_at.isoformat() if self.processed_at else None,
"processed_by_user_id": self.processed_by_user_id,
"chatbot_id": self.chatbot_id,
"summary": self.summary,
"error_message": self.error_message,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
"zammad_created_at": self.zammad_created_at.isoformat() if self.zammad_created_at else None,
"zammad_updated_at": self.zammad_updated_at.isoformat() if self.zammad_updated_at else None,
"zammad_article_count": self.zammad_article_count
}
class ZammadProcessingLog(Base):
"""Model for logging Zammad processing activities"""
__tablename__ = "zammad_processing_logs"
# Primary key
id = Column(Integer, primary_key=True, index=True)
# Processing batch information
batch_id = Column(String, index=True, nullable=False) # UUID for batch processing
started_at = Column(DateTime, default=datetime.utcnow, nullable=False)
completed_at = Column(DateTime, nullable=True)
initiated_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
# Processing configuration
config_used = Column(JSON, nullable=True)
filters_applied = Column(JSON, nullable=True) # State, limit, etc.
# Results
tickets_found = Column(Integer, default=0, nullable=False)
tickets_processed = Column(Integer, default=0, nullable=False)
tickets_failed = Column(Integer, default=0, nullable=False)
tickets_skipped = Column(Integer, default=0, nullable=False)
# Performance metrics
processing_time_seconds = Column(Integer, nullable=True)
average_time_per_ticket = Column(Integer, nullable=True) # milliseconds
# Error tracking
errors_encountered = Column(JSON, nullable=True) # List of error messages
status = Column(String, default="running", nullable=False) # running, completed, failed
# Relationships
initiated_by = relationship("User", foreign_keys=[initiated_by_user_id])
# Indexes
__table_args__ = (
Index("idx_processing_logs_batch_status", "batch_id", "status"),
Index("idx_processing_logs_user_started", "initiated_by_user_id", "started_at"),
)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for API responses"""
return {
"id": self.id,
"batch_id": self.batch_id,
"started_at": self.started_at.isoformat(),
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
"initiated_by_user_id": self.initiated_by_user_id,
"config_used": self.config_used,
"filters_applied": self.filters_applied,
"tickets_found": self.tickets_found,
"tickets_processed": self.tickets_processed,
"tickets_failed": self.tickets_failed,
"tickets_skipped": self.tickets_skipped,
"processing_time_seconds": self.processing_time_seconds,
"average_time_per_ticket": self.average_time_per_ticket,
"errors_encountered": self.errors_encountered,
"status": self.status
}
class ZammadConfiguration(Base):
"""Model for storing Zammad module configurations per user"""
__tablename__ = "zammad_configurations"
# Primary key
id = Column(Integer, primary_key=True, index=True)
# User association
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Configuration name and description
name = Column(String, nullable=False)
description = Column(Text, nullable=True)
is_default = Column(Boolean, default=False, nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
# Zammad connection settings
zammad_url = Column(String, nullable=False)
api_token_encrypted = Column(String, nullable=False) # Encrypted API token
# Processing settings
chatbot_id = Column(String, nullable=False)
process_state = Column(String, default="open", nullable=False)
max_tickets = Column(Integer, default=10, nullable=False)
skip_existing = Column(Boolean, default=True, nullable=False)
auto_process = Column(Boolean, default=False, nullable=False)
process_interval = Column(Integer, default=30, nullable=False) # minutes
# Customization
summary_template = Column(Text, nullable=True)
custom_settings = Column(JSON, nullable=True) # Additional custom settings
# Metadata
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
last_used_at = Column(DateTime, nullable=True)
# Relationships
user = relationship("User", foreign_keys=[user_id])
# Indexes
__table_args__ = (
Index("idx_zammad_config_user_active", "user_id", "is_active"),
Index("idx_zammad_config_user_default", "user_id", "is_default"),
)
def to_dict(self, include_sensitive: bool = False) -> Dict[str, Any]:
"""Convert to dictionary for API responses"""
result = {
"id": self.id,
"user_id": self.user_id,
"name": self.name,
"description": self.description,
"is_default": self.is_default,
"is_active": self.is_active,
"zammad_url": self.zammad_url,
"chatbot_id": self.chatbot_id,
"process_state": self.process_state,
"max_tickets": self.max_tickets,
"skip_existing": self.skip_existing,
"auto_process": self.auto_process,
"process_interval": self.process_interval,
"summary_template": self.summary_template,
"custom_settings": self.custom_settings,
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
"last_used_at": self.last_used_at.isoformat() if self.last_used_at else None
}
if include_sensitive:
result["api_token_encrypted"] = self.api_token_encrypted
return result