refactor: migrate modules from re-exports to canonical implementations

Move actual code implementations into module directories:
- orders: 5 services, 4 models, order/invoice schemas
- inventory: 3 services, 2 models, 30+ schemas
- customers: 3 services, 2 models, customer schemas
- messaging: 3 services, 2 models, message/notification schemas
- monitoring: background_tasks_service
- marketplace: 5+ services including letzshop submodule
- dev_tools: code_quality_service, test_runner_service
- billing: billing_service
- contracts: definition.py

Legacy files in app/services/, models/database/, models/schema/
now re-export from canonical module locations for backwards
compatibility. Architecture validator passes with 0 errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 21:28:56 +01:00
parent b5a803cde8
commit de83875d0a
99 changed files with 19413 additions and 15357 deletions

View File

@@ -2,27 +2,106 @@
"""
Messaging module Pydantic schemas.
Re-exports messaging-related schemas from their source locations.
This module contains the canonical implementations of messaging-related schemas.
"""
from models.schema.message import (
ConversationResponse,
ConversationListResponse,
MessageResponse,
MessageCreate,
from app.modules.messaging.schemas.message import (
# Attachment schemas
AttachmentResponse,
# Message schemas
MessageCreate,
MessageResponse,
# Participant schemas
ParticipantInfo,
ParticipantResponse,
# Conversation schemas
ConversationCreate,
ConversationSummary,
ConversationDetailResponse,
ConversationListResponse,
ConversationResponse,
# Unread count
UnreadCountResponse,
# Notification preferences
NotificationPreferencesUpdate,
# Conversation actions
CloseConversationResponse,
ReopenConversationResponse,
MarkReadResponse,
# Recipient selection
RecipientOption,
RecipientListResponse,
# Admin schemas
AdminConversationSummary,
AdminConversationListResponse,
AdminMessageStats,
)
from models.schema.notification import (
from app.modules.messaging.schemas.notification import (
# Response schemas
MessageResponse as NotificationMessageResponse,
UnreadCountResponse as NotificationUnreadCountResponse,
# Notification schemas
NotificationResponse,
NotificationListResponse,
# Settings schemas
NotificationSettingsResponse,
NotificationSettingsUpdate,
# Template schemas
NotificationTemplateResponse,
NotificationTemplateListResponse,
NotificationTemplateUpdate,
# Test notification
TestNotificationRequest,
# Alert statistics
AlertStatisticsResponse,
)
__all__ = [
"ConversationResponse",
"ConversationListResponse",
"MessageResponse",
"MessageCreate",
# Attachment schemas
"AttachmentResponse",
# Message schemas
"MessageCreate",
"MessageResponse",
# Participant schemas
"ParticipantInfo",
"ParticipantResponse",
# Conversation schemas
"ConversationCreate",
"ConversationSummary",
"ConversationDetailResponse",
"ConversationListResponse",
"ConversationResponse",
# Unread count
"UnreadCountResponse",
# Notification preferences
"NotificationPreferencesUpdate",
# Conversation actions
"CloseConversationResponse",
"ReopenConversationResponse",
"MarkReadResponse",
# Recipient selection
"RecipientOption",
"RecipientListResponse",
# Admin schemas
"AdminConversationSummary",
"AdminConversationListResponse",
"AdminMessageStats",
# Notification response schemas
"NotificationMessageResponse",
"NotificationUnreadCountResponse",
# Notification schemas
"NotificationResponse",
"NotificationListResponse",
# Settings schemas
"NotificationSettingsResponse",
"NotificationSettingsUpdate",
# Template schemas
"NotificationTemplateResponse",
"NotificationTemplateListResponse",
"NotificationTemplateUpdate",
# Test notification
"TestNotificationRequest",
# Alert statistics
"AlertStatisticsResponse",
]

View File

@@ -0,0 +1,312 @@
# app/modules/messaging/schemas/message.py
"""
Pydantic schemas for the messaging system.
Supports three communication channels:
- Admin <-> Vendor
- Vendor <-> Customer
- Admin <-> Customer
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from app.modules.messaging.models.message import ConversationType, ParticipantType
# ============================================================================
# Attachment Schemas
# ============================================================================
class AttachmentResponse(BaseModel):
"""Schema for message attachment in responses."""
model_config = ConfigDict(from_attributes=True)
id: int
filename: str
original_filename: str
file_size: int
mime_type: str
is_image: bool
image_width: int | None = None
image_height: int | None = None
download_url: str | None = None
thumbnail_url: str | None = None
@property
def file_size_display(self) -> str:
"""Human-readable file size."""
if self.file_size < 1024:
return f"{self.file_size} B"
elif self.file_size < 1024 * 1024:
return f"{self.file_size / 1024:.1f} KB"
else:
return f"{self.file_size / 1024 / 1024:.1f} MB"
# ============================================================================
# Message Schemas
# ============================================================================
class MessageCreate(BaseModel):
"""Schema for sending a new message."""
content: str = Field(..., min_length=1, max_length=10000)
class MessageResponse(BaseModel):
"""Schema for a single message in responses."""
model_config = ConfigDict(from_attributes=True)
id: int
conversation_id: int
sender_type: ParticipantType
sender_id: int
content: str
is_system_message: bool
is_deleted: bool
created_at: datetime
# Enriched sender info (populated by API)
sender_name: str | None = None
sender_email: str | None = None
# Attachments
attachments: list[AttachmentResponse] = []
# ============================================================================
# Participant Schemas
# ============================================================================
class ParticipantInfo(BaseModel):
"""Schema for participant information."""
id: int
type: ParticipantType
name: str
email: str | None = None
avatar_url: str | None = None
class ParticipantResponse(BaseModel):
"""Schema for conversation participant in responses."""
model_config = ConfigDict(from_attributes=True)
id: int
participant_type: ParticipantType
participant_id: int
unread_count: int
last_read_at: datetime | None
email_notifications: bool
muted: bool
# Enriched info (populated by API)
participant_info: ParticipantInfo | None = None
# ============================================================================
# Conversation Schemas
# ============================================================================
class ConversationCreate(BaseModel):
"""Schema for creating a new conversation."""
conversation_type: ConversationType
subject: str = Field(..., min_length=1, max_length=500)
recipient_type: ParticipantType
recipient_id: int
vendor_id: int | None = None
initial_message: str | None = Field(None, min_length=1, max_length=10000)
class ConversationSummary(BaseModel):
"""Schema for conversation in list views."""
model_config = ConfigDict(from_attributes=True)
id: int
conversation_type: ConversationType
subject: str
vendor_id: int | None = None
is_closed: bool
closed_at: datetime | None
last_message_at: datetime | None
message_count: int
created_at: datetime
# Unread count for current user (from participant)
unread_count: int = 0
# Other participant info (enriched by API)
other_participant: ParticipantInfo | None = None
# Last message preview
last_message_preview: str | None = None
class ConversationDetailResponse(BaseModel):
"""Schema for full conversation detail with messages."""
model_config = ConfigDict(from_attributes=True)
id: int
conversation_type: ConversationType
subject: str
vendor_id: int | None = None
is_closed: bool
closed_at: datetime | None
closed_by_type: ParticipantType | None = None
closed_by_id: int | None = None
last_message_at: datetime | None
message_count: int
created_at: datetime
updated_at: datetime
# Participants with enriched info
participants: list[ParticipantResponse] = []
# Messages ordered by created_at
messages: list[MessageResponse] = []
# Current user's unread count
unread_count: int = 0
# Vendor info if applicable
vendor_name: str | None = None
class ConversationListResponse(BaseModel):
"""Schema for paginated conversation list."""
conversations: list[ConversationSummary]
total: int
total_unread: int
skip: int
limit: int
# Backward compatibility alias
ConversationResponse = ConversationDetailResponse
# ============================================================================
# Unread Count Schemas
# ============================================================================
class UnreadCountResponse(BaseModel):
"""Schema for unread message count (for header badge)."""
total_unread: int
# ============================================================================
# Notification Preferences Schemas
# ============================================================================
class NotificationPreferencesUpdate(BaseModel):
"""Schema for updating notification preferences."""
email_notifications: bool | None = None
muted: bool | None = None
# ============================================================================
# Conversation Action Schemas
# ============================================================================
class CloseConversationResponse(BaseModel):
"""Response after closing a conversation."""
success: bool
message: str
conversation_id: int
class ReopenConversationResponse(BaseModel):
"""Response after reopening a conversation."""
success: bool
message: str
conversation_id: int
class MarkReadResponse(BaseModel):
"""Response after marking conversation as read."""
success: bool
conversation_id: int
unread_count: int
# ============================================================================
# Recipient Selection Schemas (for compose modal)
# ============================================================================
class RecipientOption(BaseModel):
"""Schema for a selectable recipient in compose modal."""
id: int
type: ParticipantType
name: str
email: str | None = None
vendor_id: int | None = None # For vendor users
vendor_name: str | None = None
class RecipientListResponse(BaseModel):
"""Schema for list of available recipients."""
recipients: list[RecipientOption]
total: int
# ============================================================================
# Admin-specific Schemas
# ============================================================================
class AdminConversationSummary(ConversationSummary):
"""Extended conversation summary with vendor info for admin views."""
vendor_name: str | None = None
vendor_code: str | None = None
class AdminConversationListResponse(BaseModel):
"""Schema for admin conversation list with vendor info."""
conversations: list[AdminConversationSummary]
total: int
total_unread: int
skip: int
limit: int
class AdminMessageStats(BaseModel):
"""Messaging statistics for admin dashboard."""
total_conversations: int = 0
open_conversations: int = 0
closed_conversations: int = 0
total_messages: int = 0
# By type
admin_vendor_conversations: int = 0
vendor_customer_conversations: int = 0
admin_customer_conversations: int = 0
# Unread
unread_admin: int = 0

View File

@@ -0,0 +1,152 @@
# app/modules/messaging/schemas/notification.py
"""
Notification Pydantic schemas for API validation and responses.
This module provides schemas for:
- Vendor notifications (list, read, delete)
- Notification settings management
- Notification email templates
- Unread counts and statistics
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, Field
# ============================================================================
# SHARED RESPONSE SCHEMAS
# ============================================================================
class MessageResponse(BaseModel):
"""Generic message response for simple operations."""
message: str
class UnreadCountResponse(BaseModel):
"""Response for unread notification count."""
unread_count: int
message: str | None = None
# ============================================================================
# NOTIFICATION SCHEMAS
# ============================================================================
class NotificationResponse(BaseModel):
"""Single notification response."""
id: int
type: str
title: str
message: str
is_read: bool
read_at: datetime | None = None
priority: str = "normal"
action_url: str | None = None
metadata: dict[str, Any] | None = None
created_at: datetime
model_config = {"from_attributes": True}
class NotificationListResponse(BaseModel):
"""Paginated list of notifications."""
notifications: list[NotificationResponse] = []
total: int = 0
unread_count: int = 0
message: str | None = None
# ============================================================================
# NOTIFICATION SETTINGS SCHEMAS
# ============================================================================
class NotificationSettingsResponse(BaseModel):
"""Notification preferences response."""
email_notifications: bool = True
in_app_notifications: bool = True
notification_types: dict[str, bool] = Field(default_factory=dict)
message: str | None = None
class NotificationSettingsUpdate(BaseModel):
"""Request model for updating notification settings."""
email_notifications: bool | None = None
in_app_notifications: bool | None = None
notification_types: dict[str, bool] | None = None
# ============================================================================
# NOTIFICATION TEMPLATE SCHEMAS
# ============================================================================
class NotificationTemplateResponse(BaseModel):
"""Single notification template response."""
id: int
name: str
type: str
subject: str
body_html: str | None = None
body_text: str | None = None
variables: list[str] = Field(default_factory=list)
is_active: bool = True
created_at: datetime
updated_at: datetime | None = None
model_config = {"from_attributes": True}
class NotificationTemplateListResponse(BaseModel):
"""List of notification templates."""
templates: list[NotificationTemplateResponse] = []
message: str | None = None
class NotificationTemplateUpdate(BaseModel):
"""Request model for updating notification template."""
subject: str | None = Field(None, max_length=200)
body_html: str | None = None
body_text: str | None = None
is_active: bool | None = None
# ============================================================================
# TEST NOTIFICATION SCHEMA
# ============================================================================
class TestNotificationRequest(BaseModel):
"""Request model for sending test notification."""
template_id: int | None = Field(None, description="Template to use")
email: str | None = Field(None, description="Override recipient email")
notification_type: str = Field(
default="test", description="Type of notification to send"
)
# ============================================================================
# ADMIN ALERT STATISTICS SCHEMA
# ============================================================================
class AlertStatisticsResponse(BaseModel):
"""Response for alert statistics."""
total_alerts: int = 0
active_alerts: int = 0
critical_alerts: int = 0
resolved_today: int = 0