Update routes, services, and tests

This commit is contained in:
Dries Peeters
2025-11-29 07:22:15 +01:00
parent 583f9b6755
commit 0094428b72
11 changed files with 51 additions and 13 deletions

View File

@@ -632,7 +632,15 @@ def create_app(config=None):
app.logger.warning(f"Failed to initialize PostHog: {e}")
# Fail-fast on weak/missing secret in production
if not app.debug and app.config.get("FLASK_ENV", "production") == "production":
# Skip validation in testing or debug mode
is_testing = app.config.get("TESTING", False)
# Check both config and environment variable for FLASK_ENV
flask_env_config = app.config.get("FLASK_ENV")
flask_env_env = os.getenv("FLASK_ENV", "production")
flask_env = flask_env_config if flask_env_config else flask_env_env
is_production_env = flask_env == "production" and not is_testing
if not app.debug and is_production_env:
secret = app.config.get("SECRET_KEY")
placeholder_values = {
"dev-secret-key-change-in-production",

View File

@@ -882,7 +882,7 @@ def new_transfer():
reason = f"Transfer from {from_warehouse.code} to {to_warehouse.code}"
# Create negative movement (from source warehouse)
out_movement, _ = StockMovement.record_movement(
out_movement, _unused = StockMovement.record_movement(
movement_type="transfer",
stock_item_id=stock_item_id,
warehouse_id=from_warehouse_id,
@@ -896,7 +896,7 @@ def new_transfer():
)
# Create positive movement (to destination warehouse)
in_movement, _ = StockMovement.record_movement(
in_movement, _unused = StockMovement.record_movement(
movement_type="transfer",
stock_item_id=stock_item_id,
warehouse_id=to_warehouse_id,

View File

@@ -14,6 +14,7 @@ from app import db, log_event, track_event
from app.services import InvoiceService, ProjectService
from app.repositories import InvoiceRepository, ProjectRepository
from app.models import Invoice, Project, Settings
from app.utils.db import safe_commit
from app.utils.api_responses import success_response, error_response, paginated_response
from app.utils.event_bus import emit_event
from app.constants import WebhookEvent, InvoiceStatus

View File

@@ -349,7 +349,7 @@ def create_project():
entity_type="project",
entity_id=project.id,
entity_name=project.name,
description=f'Created project "{project.name}" for {client.name}',
description=f'Created project "{project.name}" for {project.client.name}',
ip_address=request.remote_addr,
user_agent=request.headers.get("User-Agent"),
)

View File

@@ -11,8 +11,8 @@ from flask_login import login_required, current_user
from sqlalchemy.orm import joinedload
from app import db
from app.services import ProjectService
from app.repositories import ProjectRepository, ClientRepository
from app.models import Project, Client, UserFavoriteProject
from app.repositories import ProjectRepository, ClientRepository, TimeEntryRepository
from app.models import Project, Client, UserFavoriteProject, TimeEntry
from app.utils.permissions import admin_or_permission_required
projects_bp = Blueprint("projects", __name__)

View File

@@ -2,11 +2,12 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
from flask_babel import gettext as _
from flask_login import login_required, current_user
from app import db, log_event, track_event
from app.models import Quote, QuoteItem, QuoteAttachment, Client, Project, Invoice
from app.models import Quote, QuoteItem, QuoteAttachment, Client, Project, Invoice, QuoteTemplate
from datetime import datetime
from decimal import Decimal, InvalidOperation
from app.utils.db import safe_commit
from app.utils.permissions import admin_or_permission_required, permission_required
from app.utils.config_manager import get_setting
quotes_bp = Blueprint("quotes", __name__)
@@ -61,6 +62,11 @@ def create_quote():
valid_until = request.form.get("valid_until", "").strip()
notes = request.form.get("notes", "").strip()
terms = request.form.get("terms", "").strip()
payment_terms = request.form.get("payment_terms", "").strip()
discount_type = request.form.get("discount_type", "").strip()
discount_amount = request.form.get("discount_amount", "").strip()
discount_reason = request.form.get("discount_reason", "").strip()
coupon_code = request.form.get("coupon_code", "").strip()
try:
current_app.logger.info(
@@ -1084,6 +1090,10 @@ def create_template():
@admin_or_permission_required("create_quotes")
def save_template_from_quote(template_id):
"""Save current quote as a template"""
quote_id = request.form.get("quote_id", type=int)
if not quote_id:
flash(_("Quote ID is required"), "error")
return redirect(url_for("quotes.list_templates"))
quote = Quote.query.get_or_404(quote_id)
# Check permissions

View File

@@ -4,7 +4,7 @@ Uses pattern matching and heuristics (can be extended with actual AI APIs)
"""
from typing import Dict, List, Any, Optional
from datetime import datetime
from datetime import datetime, timedelta
from app import db
from app.models import TimeEntry, Project, Task, Client
from sqlalchemy import func

View File

@@ -23,8 +23,11 @@ def admin_user(app):
@pytest.fixture
def authenticated_admin_client(client, admin_user):
"""Create an authenticated admin client."""
# Use the actual login endpoint to properly authenticate
client.post("/login", data={"username": admin_user.username}, follow_redirects=True)
from flask_login import login_user
with client.session_transaction() as sess:
# Use Flask-Login's login_user directly for tests
login_user(admin_user)
return client

View File

@@ -277,7 +277,7 @@ def test_pdf_generation_with_default_template(app, sample_invoice):
@pytest.mark.smoke
@pytest.mark.admin
def test_pdf_layout_navigation_link_exists(admin_authenticated_client):
def test_pdf_layout_navigation_link_exists(admin_authenticated_client, app):
"""Test that PDF layout link exists in admin navigation."""
# Access admin dashboard or any admin page
response = admin_authenticated_client.get("/admin/settings")
@@ -285,6 +285,11 @@ def test_pdf_layout_navigation_link_exists(admin_authenticated_client):
assert response.status_code == 200
# Should contain link to PDF layout page
# The link might be in the navigation or as a menu item
html = response.get_data(as_text=True)
# Check for PDF layout link - it's in a dropdown menu
with app.app_context():
pdf_layout_url = url_for("admin.pdf_layout")
assert "admin.pdf_layout" in html or "pdf-layout" in html or "PDF Templates" in html or pdf_layout_url in html
@pytest.mark.smoke

View File

@@ -7,6 +7,7 @@ previous time entries with pre-filled data.
import pytest
from datetime import datetime, timedelta
from flask import url_for
from app import db
from app.models import TimeEntry, User, Project, Task
@@ -272,12 +273,19 @@ def test_admin_can_duplicate_any_entry(admin_authenticated_client, user, project
def test_duplicate_button_on_dashboard(authenticated_client, time_entry_with_all_fields, app):
"""Smoke test: Duplicate button should appear on dashboard."""
with app.app_context():
# Clear any cache that might affect the dashboard
from app.utils.cache import get_cache
cache = get_cache()
cache.delete(f"dashboard:{time_entry_with_all_fields.user_id}")
response = authenticated_client.get("/dashboard")
assert response.status_code == 200
html = response.get_data(as_text=True)
# Check for duplicate button/link (may use icon or text)
assert "fa-copy" in html or "duplicate" in html.lower()
# The button uses fa-copy icon and duplicate_timer route
duplicate_url = url_for("timer.duplicate_timer", timer_id=time_entry_with_all_fields.id)
assert "fa-copy" in html or "duplicate" in html.lower() or "duplicate_timer" in html or duplicate_url in html
@pytest.mark.smoke

View File

@@ -34,8 +34,11 @@ def admin_user(app):
@pytest.fixture
def authenticated_admin_client(client, admin_user):
"""Create an authenticated admin client."""
from flask_login import login_user
with client.session_transaction() as sess:
sess["_user_id"] = str(admin_user.id)
# Use Flask-Login's login_user directly for tests
login_user(admin_user)
return client