feat: Add Linux deployment scripts and server logs view
- Add deployment/linux/ with deploy.sh for deploying from Claude-Agent LXC to Windows server - Add ServerLogsView.vue for viewing server logs from frontend - Add shared/routes/system.py for system health endpoints - Update CLAUDE.md with quick deploy instructions - Improve Windows deployment scripts (ROA2WEB-Console.ps1) - Fix OCR service validation and worker pool improvements - Update environment config examples - Various script permission and startup fixes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import UploadFile
|
||||
|
||||
from backend.modules.data_entry.db.models.receipt import ReceiptAttachment
|
||||
from backend.modules.data_entry.config import settings
|
||||
from backend.config import settings
|
||||
|
||||
|
||||
class AttachmentCRUD:
|
||||
@@ -29,7 +29,7 @@ class AttachmentCRUD:
|
||||
"""Get full path for storing file, organized by year/month."""
|
||||
now = datetime.utcnow()
|
||||
relative_path = Path(str(now.year)) / f"{now.month:02d}"
|
||||
full_path = settings.upload_path_resolved / relative_path
|
||||
full_path = settings.data_entry_upload_path_resolved / relative_path
|
||||
|
||||
# Ensure directory exists
|
||||
full_path.mkdir(parents=True, exist_ok=True)
|
||||
@@ -50,19 +50,19 @@ class AttachmentCRUD:
|
||||
relative_path = AttachmentCRUD._get_upload_path(stored_filename)
|
||||
|
||||
# Full path for saving
|
||||
full_path = settings.upload_path_resolved / relative_path
|
||||
full_path = settings.data_entry_upload_path_resolved / relative_path
|
||||
|
||||
# Read file content
|
||||
content = await file.read()
|
||||
file_size = len(content)
|
||||
|
||||
# Validate file size
|
||||
if file_size > settings.max_upload_size_bytes:
|
||||
raise ValueError(f"File too large. Maximum size is {settings.max_upload_size_mb}MB")
|
||||
if file_size > settings.data_entry_max_upload_size_bytes:
|
||||
raise ValueError(f"File too large. Maximum size is {settings.data_entry_max_upload_size_mb}MB")
|
||||
|
||||
# Validate MIME type
|
||||
mime_type = file.content_type or "application/octet-stream"
|
||||
if mime_type not in settings.allowed_mime_types:
|
||||
if mime_type not in settings.data_entry_allowed_mime_types:
|
||||
raise ValueError(f"File type not allowed: {mime_type}")
|
||||
|
||||
# Save file
|
||||
@@ -111,7 +111,7 @@ class AttachmentCRUD:
|
||||
@staticmethod
|
||||
def get_file_path(attachment: ReceiptAttachment) -> Path:
|
||||
"""Get full file path for an attachment."""
|
||||
return settings.upload_path_resolved / attachment.file_path
|
||||
return settings.data_entry_upload_path_resolved / attachment.file_path
|
||||
|
||||
@staticmethod
|
||||
async def delete(session: AsyncSession, attachment: ReceiptAttachment) -> bool:
|
||||
|
||||
@@ -193,6 +193,14 @@ class ReceiptCRUD:
|
||||
"""Update receipt fields."""
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
|
||||
# Recalculate tva_total from tva_breakdown if breakdown is being updated
|
||||
if 'tva_breakdown' in update_data and update_data['tva_breakdown']:
|
||||
tva_total = sum(
|
||||
float(entry.get('amount', 0) if isinstance(entry, dict) else getattr(entry, 'amount', 0))
|
||||
for entry in update_data['tva_breakdown']
|
||||
)
|
||||
update_data['tva_total'] = round(tva_total, 2)
|
||||
|
||||
# Serialize tva_breakdown and payment_methods to JSON string if present
|
||||
if 'tva_breakdown' in update_data:
|
||||
update_data['tva_breakdown'] = _serialize_tva_breakdown(update_data['tva_breakdown'])
|
||||
|
||||
@@ -6,13 +6,13 @@ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from backend.modules.data_entry.config import settings
|
||||
from backend.config import settings
|
||||
|
||||
|
||||
# Create async engine
|
||||
# Note: echo=False to disable SQL query logging (too verbose)
|
||||
engine = create_async_engine(
|
||||
settings.database_url,
|
||||
settings.data_entry_database_url,
|
||||
echo=False,
|
||||
future=True,
|
||||
)
|
||||
@@ -28,7 +28,7 @@ async_session_maker = sessionmaker(
|
||||
async def init_db() -> None:
|
||||
"""Initialize database - create tables if they don't exist."""
|
||||
# Ensure data directory exists
|
||||
db_path = Path(settings.sqlite_database_path)
|
||||
db_path = Path(settings.data_entry_sqlite_database_path)
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
async with engine.begin() as conn:
|
||||
|
||||
Reference in New Issue
Block a user