feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"""
|
|
|
|
|
Klippekort (Prepaid Time Card) Service
|
|
|
|
|
=======================================
|
|
|
|
|
|
|
|
|
|
Business logic for prepaid time cards: purchase, balance, deduction.
|
|
|
|
|
|
2026-01-10 21:09:29 +01:00
|
|
|
NOTE: As of migration 065, customers can have multiple active cards simultaneously.
|
|
|
|
|
When multiple active cards exist, operations default to the card with earliest expiry.
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
from typing import Optional, Dict, Any, List
|
|
|
|
|
|
2025-12-16 15:36:11 +01:00
|
|
|
from app.core.database import execute_query, execute_insert, execute_update, execute_query_single
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
from app.ticket.backend.models import (
|
|
|
|
|
TPrepaidCard,
|
|
|
|
|
TPrepaidCardCreate,
|
|
|
|
|
TPrepaidCardUpdate,
|
|
|
|
|
TPrepaidCardWithStats,
|
|
|
|
|
TPrepaidTransaction,
|
|
|
|
|
TPrepaidTransactionCreate,
|
|
|
|
|
PrepaidCardStatus,
|
|
|
|
|
TransactionType
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KlippekortService:
|
|
|
|
|
"""Service for prepaid card operations"""
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def purchase_card(
|
|
|
|
|
card_data: TPrepaidCardCreate,
|
|
|
|
|
user_id: Optional[int] = None
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Purchase a new prepaid card
|
|
|
|
|
|
2026-01-10 21:09:29 +01:00
|
|
|
Note: As of migration 065, customers can have multiple active cards simultaneously.
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
card_data: Card purchase data
|
|
|
|
|
user_id: User making purchase
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Created card dict
|
|
|
|
|
"""
|
|
|
|
|
from psycopg2.extras import Json
|
|
|
|
|
|
|
|
|
|
logger.info(f"💳 Purchasing prepaid card for customer {card_data.customer_id}: {card_data.purchased_hours}h")
|
|
|
|
|
|
|
|
|
|
# Insert card (trigger will auto-generate card_number if NULL)
|
|
|
|
|
card_id = execute_insert(
|
|
|
|
|
"""
|
|
|
|
|
INSERT INTO tticket_prepaid_cards (
|
|
|
|
|
card_number, customer_id, purchased_hours, price_per_hour, total_amount,
|
|
|
|
|
status, expires_at, notes, economic_invoice_number, economic_product_number,
|
|
|
|
|
created_by_user_id
|
|
|
|
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
|
|
|
""",
|
|
|
|
|
(
|
|
|
|
|
card_data.card_number,
|
|
|
|
|
card_data.customer_id,
|
|
|
|
|
card_data.purchased_hours,
|
|
|
|
|
card_data.price_per_hour,
|
|
|
|
|
card_data.total_amount,
|
|
|
|
|
'active',
|
|
|
|
|
card_data.expires_at,
|
|
|
|
|
card_data.notes,
|
|
|
|
|
card_data.economic_invoice_number,
|
|
|
|
|
card_data.economic_product_number,
|
|
|
|
|
user_id or card_data.created_by_user_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Create initial transaction
|
|
|
|
|
execute_insert(
|
|
|
|
|
"""
|
|
|
|
|
INSERT INTO tticket_prepaid_transactions
|
|
|
|
|
(card_id, transaction_type, hours, balance_after, description, created_by_user_id)
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s)
|
|
|
|
|
""",
|
|
|
|
|
(
|
|
|
|
|
card_id,
|
|
|
|
|
'purchase',
|
|
|
|
|
card_data.purchased_hours,
|
|
|
|
|
card_data.purchased_hours,
|
|
|
|
|
f"Initial purchase: {card_data.purchased_hours}h @ {card_data.price_per_hour} DKK/h",
|
|
|
|
|
user_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Fetch created card
|
2025-12-16 15:36:11 +01:00
|
|
|
card = execute_query_single(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"SELECT * FROM tticket_prepaid_cards WHERE id = %s",
|
2025-12-16 15:36:11 +01:00
|
|
|
(card_id,))
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
logger.info(f"✅ Created prepaid card {card['card_number']} (ID: {card_id})")
|
|
|
|
|
return card
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_card(card_id: int) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""Get prepaid card by ID"""
|
2025-12-16 15:36:11 +01:00
|
|
|
return execute_query_single(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"SELECT * FROM tticket_prepaid_cards WHERE id = %s",
|
2025-12-16 15:36:11 +01:00
|
|
|
(card_id,))
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_card_with_stats(card_id: int) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""Get prepaid card with usage statistics"""
|
2025-12-16 15:36:11 +01:00
|
|
|
return execute_query_single(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"SELECT * FROM tticket_prepaid_balances WHERE id = %s",
|
2025-12-16 15:36:11 +01:00
|
|
|
(card_id,))
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
@staticmethod
|
2026-01-10 21:09:29 +01:00
|
|
|
def get_active_cards_for_customer(customer_id: int) -> List[Dict[str, Any]]:
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"""
|
2026-01-10 21:09:29 +01:00
|
|
|
Get all active prepaid cards for customer (sorted by expiry)
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
2026-01-10 21:09:29 +01:00
|
|
|
Returns empty list if no active cards exist.
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"""
|
2026-01-10 21:09:29 +01:00
|
|
|
cards = execute_query(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"""
|
|
|
|
|
SELECT * FROM tticket_prepaid_cards
|
|
|
|
|
WHERE customer_id = %s AND status = 'active'
|
2026-01-10 21:09:29 +01:00
|
|
|
ORDER BY expires_at ASC NULLS LAST, created_at ASC
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
""",
|
2025-12-16 15:36:11 +01:00
|
|
|
(customer_id,))
|
2026-01-10 21:09:29 +01:00
|
|
|
return cards or []
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_active_card_for_customer(customer_id: int) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
Get active prepaid card for customer (defaults to earliest expiry if multiple)
|
|
|
|
|
|
|
|
|
|
Returns None if no active card exists.
|
|
|
|
|
"""
|
|
|
|
|
cards = KlippekortService.get_active_cards_for_customer(customer_id)
|
|
|
|
|
return cards[0] if cards else None
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def check_balance(customer_id: int) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Check prepaid card balance for customer
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
customer_id: Customer ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict with balance info: {
|
|
|
|
|
"has_card": bool,
|
|
|
|
|
"card_id": int or None,
|
|
|
|
|
"card_number": str or None,
|
|
|
|
|
"balance_hours": Decimal,
|
|
|
|
|
"status": str or None,
|
|
|
|
|
"expires_at": datetime or None
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
card = KlippekortService.get_active_card_for_customer(customer_id)
|
|
|
|
|
|
|
|
|
|
if not card:
|
|
|
|
|
return {
|
|
|
|
|
"has_card": False,
|
|
|
|
|
"card_id": None,
|
|
|
|
|
"card_number": None,
|
|
|
|
|
"balance_hours": Decimal('0'),
|
|
|
|
|
"status": None,
|
|
|
|
|
"expires_at": None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"has_card": True,
|
|
|
|
|
"card_id": card['id'],
|
|
|
|
|
"card_number": card['card_number'],
|
|
|
|
|
"balance_hours": card['remaining_hours'],
|
|
|
|
|
"status": card['status'],
|
|
|
|
|
"expires_at": card['expires_at']
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def can_deduct(customer_id: int, hours: Decimal) -> tuple[bool, Optional[str]]:
|
|
|
|
|
"""
|
|
|
|
|
Check if customer has sufficient balance for deduction
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
customer_id: Customer ID
|
|
|
|
|
hours: Hours to deduct
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
(can_deduct, error_message)
|
|
|
|
|
"""
|
|
|
|
|
balance_info = KlippekortService.check_balance(customer_id)
|
|
|
|
|
|
|
|
|
|
if not balance_info['has_card']:
|
|
|
|
|
return False, f"Customer {customer_id} has no active prepaid card"
|
|
|
|
|
|
|
|
|
|
if balance_info['status'] != 'active':
|
|
|
|
|
return False, f"Prepaid card is not active (status: {balance_info['status']})"
|
|
|
|
|
|
|
|
|
|
if balance_info['balance_hours'] < hours:
|
|
|
|
|
return False, (
|
|
|
|
|
f"Insufficient balance: {balance_info['balance_hours']}h available, "
|
|
|
|
|
f"{hours}h required"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Check expiration
|
|
|
|
|
if balance_info['expires_at']:
|
|
|
|
|
if balance_info['expires_at'] < datetime.now():
|
|
|
|
|
return False, f"Prepaid card expired on {balance_info['expires_at']}"
|
|
|
|
|
|
|
|
|
|
return True, None
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def deduct_hours(
|
|
|
|
|
customer_id: int,
|
|
|
|
|
hours: Decimal,
|
|
|
|
|
worklog_id: int,
|
|
|
|
|
user_id: Optional[int] = None,
|
|
|
|
|
description: Optional[str] = None
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Deduct hours from customer's active prepaid card
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
customer_id: Customer ID
|
|
|
|
|
hours: Hours to deduct
|
|
|
|
|
worklog_id: Worklog entry consuming the hours
|
|
|
|
|
user_id: User performing deduction
|
|
|
|
|
description: Optional description
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Transaction dict
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
ValueError: If insufficient balance or no active card
|
|
|
|
|
"""
|
|
|
|
|
# Check if deduction is possible
|
|
|
|
|
can_deduct, error = KlippekortService.can_deduct(customer_id, hours)
|
|
|
|
|
if not can_deduct:
|
|
|
|
|
raise ValueError(error)
|
|
|
|
|
|
|
|
|
|
# Get active card
|
|
|
|
|
card = KlippekortService.get_active_card_for_customer(customer_id)
|
|
|
|
|
|
|
|
|
|
logger.info(f"⏱️ Deducting {hours}h from card {card['card_number']} for worklog {worklog_id}")
|
|
|
|
|
|
|
|
|
|
# Update card usage
|
|
|
|
|
new_used = Decimal(str(card['used_hours'])) + hours
|
|
|
|
|
execute_update(
|
|
|
|
|
"UPDATE tticket_prepaid_cards SET used_hours = %s WHERE id = %s",
|
|
|
|
|
(new_used, card['id'])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Calculate new balance
|
|
|
|
|
new_balance = Decimal(str(card['remaining_hours'])) - hours
|
|
|
|
|
|
|
|
|
|
# Create transaction
|
|
|
|
|
transaction_id = execute_insert(
|
|
|
|
|
"""
|
|
|
|
|
INSERT INTO tticket_prepaid_transactions
|
|
|
|
|
(card_id, worklog_id, transaction_type, hours, balance_after, description, created_by_user_id)
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
|
|
|
""",
|
|
|
|
|
(
|
|
|
|
|
card['id'],
|
|
|
|
|
worklog_id,
|
|
|
|
|
'usage',
|
|
|
|
|
-hours, # Negative for deduction
|
|
|
|
|
new_balance,
|
|
|
|
|
description or f"Worklog #{worklog_id}: {hours}h",
|
|
|
|
|
user_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Check if card is now depleted
|
|
|
|
|
if new_balance <= Decimal('0'):
|
|
|
|
|
execute_update(
|
|
|
|
|
"UPDATE tticket_prepaid_cards SET status = 'depleted' WHERE id = %s",
|
|
|
|
|
(card['id'],)
|
|
|
|
|
)
|
|
|
|
|
logger.warning(f"💳 Card {card['card_number']} is now depleted")
|
|
|
|
|
|
|
|
|
|
# Fetch transaction
|
2025-12-16 15:36:11 +01:00
|
|
|
transaction = execute_query_single(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"SELECT * FROM tticket_prepaid_transactions WHERE id = %s",
|
2025-12-16 15:36:11 +01:00
|
|
|
(transaction_id,))
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
logger.info(f"✅ Deducted {hours}h from card {card['card_number']}, new balance: {new_balance}h")
|
|
|
|
|
return transaction
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def top_up_card(
|
|
|
|
|
card_id: int,
|
|
|
|
|
hours: Decimal,
|
|
|
|
|
user_id: Optional[int] = None,
|
|
|
|
|
note: Optional[str] = None
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Add hours to existing prepaid card
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
card_id: Card ID
|
|
|
|
|
hours: Hours to add
|
|
|
|
|
user_id: User performing top-up
|
|
|
|
|
note: Optional note
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Transaction dict
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
ValueError: If card not found or not active
|
|
|
|
|
"""
|
|
|
|
|
# Get card
|
|
|
|
|
card = KlippekortService.get_card(card_id)
|
|
|
|
|
|
|
|
|
|
if not card:
|
|
|
|
|
raise ValueError(f"Prepaid card {card_id} not found")
|
|
|
|
|
|
|
|
|
|
if card['status'] not in ['active', 'depleted']:
|
|
|
|
|
raise ValueError(f"Cannot top up card with status: {card['status']}")
|
|
|
|
|
|
|
|
|
|
logger.info(f"💰 Topping up card {card['card_number']} with {hours}h")
|
|
|
|
|
|
|
|
|
|
# Update purchased hours
|
|
|
|
|
new_purchased = Decimal(str(card['purchased_hours'])) + hours
|
|
|
|
|
execute_update(
|
|
|
|
|
"UPDATE tticket_prepaid_cards SET purchased_hours = %s, status = 'active' WHERE id = %s",
|
|
|
|
|
(new_purchased, card_id)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Calculate new balance
|
|
|
|
|
new_balance = Decimal(str(card['remaining_hours'])) + hours
|
|
|
|
|
|
|
|
|
|
# Create transaction
|
|
|
|
|
transaction_id = execute_insert(
|
|
|
|
|
"""
|
|
|
|
|
INSERT INTO tticket_prepaid_transactions
|
|
|
|
|
(card_id, transaction_type, hours, balance_after, description, created_by_user_id)
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s)
|
|
|
|
|
""",
|
|
|
|
|
(
|
|
|
|
|
card_id,
|
|
|
|
|
'top_up',
|
|
|
|
|
hours, # Positive for addition
|
|
|
|
|
new_balance,
|
|
|
|
|
note or f"Top-up: {hours}h added",
|
|
|
|
|
user_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-16 15:36:11 +01:00
|
|
|
transaction = execute_query_single(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"SELECT * FROM tticket_prepaid_transactions WHERE id = %s",
|
2025-12-16 15:36:11 +01:00
|
|
|
(transaction_id,))
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
logger.info(f"✅ Topped up card {card['card_number']} with {hours}h, new balance: {new_balance}h")
|
|
|
|
|
return transaction
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def get_transactions(
|
|
|
|
|
card_id: int,
|
|
|
|
|
limit: int = 100
|
|
|
|
|
) -> List[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
Get transaction history for card
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
card_id: Card ID
|
|
|
|
|
limit: Max number of transactions
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of transaction dicts
|
|
|
|
|
"""
|
2025-12-16 15:36:11 +01:00
|
|
|
transactions = execute_query_single(
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
"""
|
|
|
|
|
SELECT * FROM tticket_prepaid_transactions
|
|
|
|
|
WHERE card_id = %s
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
LIMIT %s
|
|
|
|
|
""",
|
|
|
|
|
(card_id, limit)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return transactions or []
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def list_cards(
|
|
|
|
|
customer_id: Optional[int] = None,
|
|
|
|
|
status: Optional[str] = None,
|
|
|
|
|
limit: int = 50,
|
|
|
|
|
offset: int = 0
|
|
|
|
|
) -> List[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
List prepaid cards with optional filters
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
customer_id: Filter by customer
|
|
|
|
|
status: Filter by status
|
|
|
|
|
limit: Number of results
|
|
|
|
|
offset: Offset for pagination
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of card dicts
|
|
|
|
|
"""
|
|
|
|
|
query = "SELECT * FROM tticket_prepaid_cards WHERE 1=1"
|
|
|
|
|
params = []
|
|
|
|
|
|
|
|
|
|
if customer_id:
|
|
|
|
|
query += " AND customer_id = %s"
|
|
|
|
|
params.append(customer_id)
|
|
|
|
|
|
|
|
|
|
if status:
|
|
|
|
|
query += " AND status = %s"
|
|
|
|
|
params.append(status)
|
|
|
|
|
|
|
|
|
|
query += " ORDER BY purchased_at DESC LIMIT %s OFFSET %s"
|
|
|
|
|
params.extend([limit, offset])
|
|
|
|
|
|
|
|
|
|
cards = execute_query(query, tuple(params))
|
|
|
|
|
return cards or []
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def cancel_card(
|
|
|
|
|
card_id: int,
|
|
|
|
|
user_id: Optional[int] = None,
|
|
|
|
|
reason: Optional[str] = None
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
Cancel/deactivate a prepaid card
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
card_id: Card ID
|
|
|
|
|
user_id: User cancelling card
|
|
|
|
|
reason: Cancellation reason
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Updated card dict
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
ValueError: If card not found or already cancelled
|
|
|
|
|
"""
|
|
|
|
|
card = KlippekortService.get_card(card_id)
|
|
|
|
|
|
|
|
|
|
if not card:
|
|
|
|
|
raise ValueError(f"Prepaid card {card_id} not found")
|
|
|
|
|
|
|
|
|
|
if card['status'] == 'cancelled':
|
|
|
|
|
raise ValueError(f"Card {card['card_number']} is already cancelled")
|
|
|
|
|
|
|
|
|
|
logger.info(f"❌ Cancelling card {card['card_number']}")
|
|
|
|
|
|
|
|
|
|
# Update status
|
|
|
|
|
execute_update(
|
|
|
|
|
"UPDATE tticket_prepaid_cards SET status = 'cancelled' WHERE id = %s",
|
|
|
|
|
(card_id,)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Log transaction
|
|
|
|
|
execute_insert(
|
|
|
|
|
"""
|
|
|
|
|
INSERT INTO tticket_prepaid_transactions
|
|
|
|
|
(card_id, transaction_type, hours, balance_after, description, created_by_user_id)
|
|
|
|
|
VALUES (%s, %s, %s, %s, %s, %s)
|
|
|
|
|
""",
|
|
|
|
|
(
|
|
|
|
|
card_id,
|
|
|
|
|
'cancellation',
|
|
|
|
|
Decimal('0'),
|
|
|
|
|
card['remaining_hours'],
|
|
|
|
|
reason or "Card cancelled",
|
|
|
|
|
user_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Fetch updated card
|
|
|
|
|
updated = execute_query(
|
|
|
|
|
"SELECT * FROM tticket_prepaid_cards WHERE id = %s",
|
2025-12-16 15:36:11 +01:00
|
|
|
(card_id,))
|
feat(ticket-module): Implement ticket system with comprehensive database schema, permissions, and testing suite
- Added migration 025 for the Ticket System, creating tables for tickets, comments, attachments, worklogs, prepaid cards, and audit logs.
- Introduced migration 026 to add ticket-related permissions to the auth system and assign them to user groups.
- Developed a test suite for the Ticket Module, validating database schema, ticket number generation, prepaid card constraints, service logic, worklog creation, audit logging, and views.
2025-12-15 23:40:23 +01:00
|
|
|
|
|
|
|
|
logger.info(f"✅ Cancelled card {card['card_number']}")
|
|
|
|
|
return updated
|