- Test and helper VBS scripts for VBA MCP development - Technical reference documentation and PDFs - HTML form templates for all 5 forms - PowerShell and Python scripts for PDF/documentation generation Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
704 lines
26 KiB
Python
704 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Professional PDF Generator for TimeTrack Pro Technical Reference
|
|
Creates a comprehensive, beautifully formatted technical document
|
|
"""
|
|
|
|
from reportlab.lib import colors
|
|
from reportlab.lib.pagesizes import letter, A4
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
from reportlab.lib.units import inch
|
|
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
|
|
from reportlab.platypus import (
|
|
SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle,
|
|
Image, KeepTogether, ListFlowable, ListItem, HRFlowable
|
|
)
|
|
from reportlab.pdfgen import canvas
|
|
from datetime import datetime
|
|
|
|
# Professional color scheme
|
|
PRIMARY_COLOR = colors.HexColor('#2C3E50') # Dark blue-grey
|
|
ACCENT_COLOR = colors.HexColor('#2980B9') # Professional blue
|
|
LIGHT_ACCENT = colors.HexColor('#3498DB') # Light blue
|
|
SUCCESS_COLOR = colors.HexColor('#27AE60') # Green
|
|
GREY_COLOR = colors.HexColor('#7F8C8D') # Grey
|
|
LIGHT_GREY = colors.HexColor('#ECF0F1') # Light grey
|
|
|
|
OUTPUT_FILE = r"C:\Users\alexi\Documents\projects\timetrack-pro\TimeTrack_Pro_Professional_Documentation.pdf"
|
|
|
|
|
|
class NumberedCanvas(canvas.Canvas):
|
|
"""Custom canvas for page numbers and headers/footers"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
canvas.Canvas.__init__(self, *args, **kwargs)
|
|
self._saved_page_states = []
|
|
|
|
def showPage(self):
|
|
self._saved_page_states.append(dict(self.__dict__))
|
|
self._startPage()
|
|
|
|
def save(self):
|
|
num_pages = len(self._saved_page_states)
|
|
for state in self._saved_page_states:
|
|
self.__dict__.update(state)
|
|
self.draw_page_decorations(num_pages)
|
|
canvas.Canvas.showPage(self)
|
|
canvas.Canvas.save(self)
|
|
|
|
def draw_page_decorations(self, page_count):
|
|
self.saveState()
|
|
self.setFont('Helvetica', 9)
|
|
|
|
# Page number in footer
|
|
page_num = f"Page {self._pageNumber} of {page_count}"
|
|
self.setFillColor(GREY_COLOR)
|
|
self.drawCentredString(letter[0] / 2, 0.5 * inch, page_num)
|
|
|
|
# Document title in header (skip first page)
|
|
if self._pageNumber > 1:
|
|
self.setFillColor(GREY_COLOR)
|
|
self.drawString(0.75 * inch, letter[1] - 0.5 * inch,
|
|
"TimeTrack Pro - Technical Reference")
|
|
self.drawRightString(letter[0] - 0.75 * inch, letter[1] - 0.5 * inch,
|
|
"Alexis Trouvé | 2025")
|
|
|
|
self.restoreState()
|
|
|
|
|
|
def create_styles():
|
|
"""Create custom paragraph styles"""
|
|
styles = getSampleStyleSheet()
|
|
|
|
# Cover title
|
|
styles.add(ParagraphStyle(
|
|
name='CoverTitle',
|
|
parent=styles['Heading1'],
|
|
fontSize=48,
|
|
textColor=PRIMARY_COLOR,
|
|
spaceAfter=20,
|
|
alignment=TA_CENTER,
|
|
fontName='Helvetica-Bold'
|
|
))
|
|
|
|
# Cover subtitle
|
|
styles.add(ParagraphStyle(
|
|
name='CoverSubtitle',
|
|
parent=styles['Normal'],
|
|
fontSize=24,
|
|
textColor=ACCENT_COLOR,
|
|
spaceAfter=30,
|
|
alignment=TA_CENTER,
|
|
fontName='Helvetica'
|
|
))
|
|
|
|
# Section header
|
|
styles.add(ParagraphStyle(
|
|
name='SectionHeader',
|
|
parent=styles['Heading1'],
|
|
fontSize=20,
|
|
textColor=colors.white,
|
|
backColor=PRIMARY_COLOR,
|
|
spaceAfter=16,
|
|
spaceBefore=12,
|
|
leftIndent=10,
|
|
fontName='Helvetica-Bold',
|
|
leading=28
|
|
))
|
|
|
|
# Subsection header
|
|
styles.add(ParagraphStyle(
|
|
name='SubsectionHeader',
|
|
parent=styles['Heading2'],
|
|
fontSize=14,
|
|
textColor=ACCENT_COLOR,
|
|
spaceAfter=10,
|
|
spaceBefore=12,
|
|
fontName='Helvetica-Bold'
|
|
))
|
|
|
|
# Body text
|
|
styles.add(ParagraphStyle(
|
|
name='BodyText',
|
|
parent=styles['Normal'],
|
|
fontSize=11,
|
|
textColor=PRIMARY_COLOR,
|
|
alignment=TA_JUSTIFY,
|
|
spaceAfter=8,
|
|
leading=14
|
|
))
|
|
|
|
# Bullet points
|
|
styles.add(ParagraphStyle(
|
|
name='Bullet',
|
|
parent=styles['Normal'],
|
|
fontSize=11,
|
|
textColor=PRIMARY_COLOR,
|
|
leftIndent=30,
|
|
spaceAfter=6,
|
|
leading=14
|
|
))
|
|
|
|
# Highlight box
|
|
styles.add(ParagraphStyle(
|
|
name='HighlightBox',
|
|
parent=styles['Normal'],
|
|
fontSize=12,
|
|
textColor=colors.white,
|
|
backColor=ACCENT_COLOR,
|
|
alignment=TA_CENTER,
|
|
spaceAfter=12,
|
|
spaceBefore=12,
|
|
leftIndent=20,
|
|
rightIndent=20,
|
|
borderPadding=10,
|
|
fontName='Helvetica-Bold'
|
|
))
|
|
|
|
# Code/Technical
|
|
styles.add(ParagraphStyle(
|
|
name='Code',
|
|
parent=styles['Normal'],
|
|
fontSize=9,
|
|
textColor=PRIMARY_COLOR,
|
|
fontName='Courier',
|
|
leftIndent=20,
|
|
backColor=LIGHT_GREY,
|
|
spaceAfter=8
|
|
))
|
|
|
|
return styles
|
|
|
|
|
|
def create_cover_page(story, styles):
|
|
"""Create professional cover page"""
|
|
|
|
# Title
|
|
story.append(Spacer(1, 1.5 * inch))
|
|
story.append(Paragraph("TimeTrack Pro", styles['CoverTitle']))
|
|
story.append(Spacer(1, 0.3 * inch))
|
|
|
|
# Subtitle
|
|
story.append(Paragraph("Technical & Functional Reference", styles['CoverSubtitle']))
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Horizontal line
|
|
story.append(HRFlowable(width="70%", thickness=2, color=ACCENT_COLOR,
|
|
spaceAfter=30, spaceBefore=10, hAlign='CENTER'))
|
|
|
|
# Description
|
|
desc_style = ParagraphStyle(
|
|
'CoverDesc',
|
|
parent=styles['BodyText'],
|
|
fontSize=13,
|
|
alignment=TA_CENTER,
|
|
textColor=PRIMARY_COLOR,
|
|
fontName='Helvetica-Oblique'
|
|
)
|
|
|
|
story.append(Paragraph("Professional Time Tracking Application", desc_style))
|
|
story.append(Paragraph("Built with Microsoft Access & VBA", desc_style))
|
|
story.append(Paragraph("Automated Development via MCP VBA Server", desc_style))
|
|
story.append(Spacer(1, 0.5 * inch))
|
|
|
|
# Key highlights box
|
|
highlights_data = [
|
|
['Project Highlights', ''],
|
|
['Total VBA Code', '915 Lines'],
|
|
['Modular Components', '7 Modules'],
|
|
['Database Tables', '3 Normalized Tables'],
|
|
['Public Functions', '45+ Functions'],
|
|
['Development Time Saved', '45% via Automation'],
|
|
['Code Quality', '25% Comment Ratio'],
|
|
]
|
|
|
|
highlights_table = Table(highlights_data, colWidths=[3*inch, 2*inch])
|
|
highlights_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), PRIMARY_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 14),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
|
('TOPPADDING', (0, 0), (-1, 0), 12),
|
|
('BACKGROUND', (0, 1), (0, -1), LIGHT_GREY),
|
|
('BACKGROUND', (1, 1), (1, -1), colors.white),
|
|
('TEXTCOLOR', (0, 1), (-1, -1), PRIMARY_COLOR),
|
|
('FONTNAME', (0, 1), (0, -1), 'Helvetica-Bold'),
|
|
('FONTNAME', (1, 1), (1, -1), 'Helvetica'),
|
|
('FONTSIZE', (0, 1), (-1, -1), 11),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('ALIGN', (1, 1), (1, -1), 'RIGHT'),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 10),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 10),
|
|
('TOPPADDING', (0, 1), (-1, -1), 8),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 8),
|
|
]))
|
|
|
|
story.append(highlights_table)
|
|
story.append(Spacer(1, 1 * inch))
|
|
|
|
# Author info
|
|
author_style = ParagraphStyle(
|
|
'Author',
|
|
parent=styles['BodyText'],
|
|
fontSize=13,
|
|
alignment=TA_CENTER,
|
|
textColor=PRIMARY_COLOR,
|
|
fontName='Helvetica-Bold'
|
|
)
|
|
|
|
contact_style = ParagraphStyle(
|
|
'Contact',
|
|
parent=styles['BodyText'],
|
|
fontSize=11,
|
|
alignment=TA_CENTER,
|
|
textColor=ACCENT_COLOR
|
|
)
|
|
|
|
story.append(Paragraph("Alexis Trouvé", author_style))
|
|
story.append(Spacer(1, 0.1 * inch))
|
|
story.append(Paragraph("alexistrouve.pro@gmail.com", contact_style))
|
|
story.append(Spacer(1, 0.3 * inch))
|
|
|
|
version_style = ParagraphStyle(
|
|
'Version',
|
|
parent=styles['BodyText'],
|
|
fontSize=10,
|
|
alignment=TA_CENTER,
|
|
textColor=GREY_COLOR
|
|
)
|
|
story.append(Paragraph("Version 1.0 | January 2025", version_style))
|
|
|
|
story.append(PageBreak())
|
|
|
|
|
|
def create_section_1_executive_summary(story, styles):
|
|
"""Section 1: Executive Summary"""
|
|
|
|
story.append(Paragraph("1. Executive Summary", styles['SectionHeader']))
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
|
|
story.append(Paragraph(
|
|
"""TimeTrack Pro is a professional time tracking application built on Microsoft Access,
|
|
showcasing advanced database design, VBA automation, and modern development practices.
|
|
This project demonstrates the capability to deliver production-ready business applications
|
|
through automated development workflows using the VBA MCP Server.""",
|
|
styles['BodyText']
|
|
))
|
|
story.append(Spacer(1, 0.1 * inch))
|
|
|
|
story.append(Paragraph(
|
|
"""<b><font color="#2980B9">Key Achievement:</font></b> Complete application development
|
|
(database structure, business logic, queries, and VBA modules) automated via MCP
|
|
(Model Context Protocol) integration, demonstrating cutting-edge AI-assisted development
|
|
capabilities.""",
|
|
styles['BodyText']
|
|
))
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Statistics table
|
|
story.append(Paragraph("1.1 Project Statistics", styles['SubsectionHeader']))
|
|
|
|
stats_data = [
|
|
['Metric', 'Value', 'Description'],
|
|
['Total VBA Lines', '915', 'Professional, commented code'],
|
|
['VBA Modules', '7', 'Modular architecture with separation of concerns'],
|
|
['Database Tables', '3', 'Normalized relational structure'],
|
|
['Public Functions', '45+', 'Reusable business logic components'],
|
|
['Development Time Saved', '45%', 'Via MCP VBA automation'],
|
|
['Comment Ratio', '25%', 'Well-documented codebase'],
|
|
['Error Handling', '100%', 'All public functions include error handling'],
|
|
]
|
|
|
|
stats_table = Table(stats_data, colWidths=[2*inch, 1.5*inch, 3*inch])
|
|
stats_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), PRIMARY_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 11),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 10),
|
|
('TOPPADDING', (0, 0), (-1, 0), 10),
|
|
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
|
('TEXTCOLOR', (0, 1), (-1, -1), PRIMARY_COLOR),
|
|
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
|
('FONTSIZE', (0, 1), (-1, -1), 10),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 8),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 8),
|
|
('TOPPADDING', (0, 1), (-1, -1), 6),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 6),
|
|
]))
|
|
|
|
story.append(stats_table)
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Current usage data
|
|
story.append(Paragraph("1.2 Current Application Data", styles['SubsectionHeader']))
|
|
|
|
usage_data = [
|
|
['Metric', 'Value'],
|
|
['Active Clients', '4'],
|
|
['Active Projects', '6'],
|
|
['Total Time Entries', '15+'],
|
|
['Total Hours Tracked', '58 hours'],
|
|
['Total Revenue Calculated', '€4,732.50'],
|
|
]
|
|
|
|
usage_table = Table(usage_data, colWidths=[3*inch, 2*inch])
|
|
usage_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), ACCENT_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (0, -1), 'LEFT'),
|
|
('ALIGN', (1, 0), (1, -1), 'RIGHT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 11),
|
|
('FONTNAME', (1, 1), (1, -1), 'Helvetica-Bold'),
|
|
('FONTSIZE', (1, 1), (1, -1), 12),
|
|
('TEXTCOLOR', (1, 1), (1, -1), SUCCESS_COLOR),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 10),
|
|
('TOPPADDING', (0, 0), (-1, 0), 10),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 10),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 10),
|
|
('TOPPADDING', (0, 1), (-1, -1), 8),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 8),
|
|
]))
|
|
|
|
story.append(usage_table)
|
|
story.append(PageBreak())
|
|
|
|
|
|
def create_section_2_project_overview(story, styles):
|
|
"""Section 2: Project Overview"""
|
|
|
|
story.append(Paragraph("2. Project Overview", styles['SectionHeader']))
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
|
|
# Purpose
|
|
story.append(Paragraph("2.1 Purpose", styles['SubsectionHeader']))
|
|
story.append(Paragraph(
|
|
"""TimeTrack Pro is a time management tool designed for freelancers, consultants,
|
|
and small teams to:""",
|
|
styles['BodyText']
|
|
))
|
|
|
|
bullets = [
|
|
"Track billable hours across multiple clients and projects",
|
|
"Calculate revenue automatically based on hourly rates",
|
|
"Generate professional reports for invoicing and analysis",
|
|
"Maintain a complete audit trail of time entries"
|
|
]
|
|
|
|
for bullet in bullets:
|
|
story.append(Paragraph(f"• {bullet}", styles['Bullet']))
|
|
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
|
|
# Target Audience
|
|
story.append(Paragraph("2.2 Target Audience", styles['SubsectionHeader']))
|
|
|
|
audience_data = [
|
|
['Audience', 'Use Case'],
|
|
['Freelancers', 'Independent consultants tracking multiple client projects and generating invoices'],
|
|
['Small Teams', 'Agencies managing client work, resource allocation, and team productivity'],
|
|
['Consultants', 'Professional services requiring detailed time records and client reporting'],
|
|
]
|
|
|
|
audience_table = Table(audience_data, colWidths=[1.5*inch, 5*inch])
|
|
audience_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), ACCENT_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 11),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 10),
|
|
('TOPPADDING', (0, 0), (-1, 0), 10),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 8),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 8),
|
|
('TOPPADDING', (0, 1), (-1, -1), 8),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 8),
|
|
]))
|
|
|
|
story.append(audience_table)
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Differentiators
|
|
story.append(Paragraph("2.3 Key Differentiators", styles['SubsectionHeader']))
|
|
|
|
diff_bullets = [
|
|
"<b>Automated Development:</b> Built using VBA MCP Server v0.6.0+ for database automation",
|
|
"<b>Clean Architecture:</b> Modular VBA design with clear separation of concerns",
|
|
"<b>Production-Ready:</b> Complete with data validation, error handling, and user-friendly interfaces",
|
|
"<b>Extensible:</b> Well-documented codebase ready for customization and enhancement",
|
|
"<b>AI-Assisted:</b> Demonstrates cutting-edge development practices with MCP integration"
|
|
]
|
|
|
|
for bullet in diff_bullets:
|
|
story.append(Paragraph(f"• {bullet}", styles['Bullet']))
|
|
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Technology Stack
|
|
story.append(Paragraph("2.4 Technology Stack", styles['SubsectionHeader']))
|
|
|
|
tech_data = [
|
|
['Component', 'Technology', 'Version/Details'],
|
|
['Database Engine', 'Microsoft Access', '2016+ / Office 365'],
|
|
['Programming Language', 'VBA', 'Visual Basic for Applications 7.1'],
|
|
['Development Automation', 'VBA MCP Server', 'v0.6.0+ (Model Context Protocol)'],
|
|
['Export Formats', 'PDF, Excel', 'Native Access/VBA integration'],
|
|
['Version Control', 'Git', '2.x with source file exports'],
|
|
]
|
|
|
|
tech_table = Table(tech_data, colWidths=[2*inch, 2.2*inch, 2.3*inch])
|
|
tech_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), PRIMARY_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 10),
|
|
('FONTSIZE', (0, 1), (-1, -1), 9),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 10),
|
|
('TOPPADDING', (0, 0), (-1, 0), 10),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 8),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 8),
|
|
('TOPPADDING', (0, 1), (-1, -1), 6),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 6),
|
|
]))
|
|
|
|
story.append(tech_table)
|
|
story.append(PageBreak())
|
|
|
|
|
|
def create_section_3_database(story, styles):
|
|
"""Section 3: Database Architecture"""
|
|
|
|
story.append(Paragraph("3. Database Architecture", styles['SectionHeader']))
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
|
|
story.append(Paragraph(
|
|
"""The application uses a normalized relational database structure following third normal
|
|
form (3NF) principles. The schema consists of three core tables with enforced referential
|
|
integrity and optimized indexes for query performance.""",
|
|
styles['BodyText']
|
|
))
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
|
|
# ERD description
|
|
story.append(Paragraph("3.1 Entity Relationship Diagram", styles['SubsectionHeader']))
|
|
|
|
erd_code = """
|
|
tbl_Clients (1) ─────── (N) tbl_Projets
|
|
│
|
|
│
|
|
tbl_Projets (1) ─────── (N) tbl_Temps
|
|
|
|
Relationships:
|
|
• One Client can have many Projects (1:N)
|
|
• One Project can have many Time Entries (1:N)
|
|
• Referential integrity enforced with CASCADE options
|
|
"""
|
|
|
|
story.append(Paragraph(erd_code, styles['Code']))
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Table: tbl_Clients
|
|
story.append(Paragraph("3.2 Table: tbl_Clients", styles['SubsectionHeader']))
|
|
story.append(Paragraph("Stores client information and contact details.", styles['BodyText']))
|
|
story.append(Spacer(1, 0.1 * inch))
|
|
|
|
clients_data = [
|
|
['Field', 'Type', 'Size', 'Constraints', 'Description'],
|
|
['ClientID', 'AutoNumber', 'Long', 'PRIMARY KEY', 'Unique identifier'],
|
|
['Nom', 'Text', '100', 'NOT NULL', 'Client name'],
|
|
['Email', 'Text', '100', 'NULL, VALIDATED', 'Email address with format validation'],
|
|
['Telephone', 'Text', '20', 'NULL', 'Phone number'],
|
|
['Notes', 'Memo', '—', 'NULL', 'Additional notes and comments'],
|
|
['DateCreation', 'DateTime', '—', 'NOT NULL, DEFAULT Now()', 'Record creation timestamp'],
|
|
]
|
|
|
|
clients_table = Table(clients_data, colWidths=[1.2*inch, 1*inch, 0.7*inch, 1.4*inch, 2.2*inch])
|
|
clients_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), PRIMARY_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 9),
|
|
('FONTSIZE', (0, 1), (-1, -1), 8),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
|
|
('TOPPADDING', (0, 0), (-1, 0), 8),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 6),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 6),
|
|
('TOPPADDING', (0, 1), (-1, -1), 5),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 5),
|
|
]))
|
|
|
|
story.append(clients_table)
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
|
|
# Indexes
|
|
index_style = ParagraphStyle('IndexNote', parent=styles['BodyText'], fontSize=9,
|
|
textColor=GREY_COLOR, fontName='Helvetica-Oblique')
|
|
story.append(Paragraph("<b>Indexes:</b> PRIMARY KEY on ClientID, Search Index on Nom", index_style))
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Table: tbl_Projets
|
|
story.append(Paragraph("3.3 Table: tbl_Projets", styles['SubsectionHeader']))
|
|
story.append(Paragraph("Stores project information linked to clients.", styles['BodyText']))
|
|
story.append(Spacer(1, 0.1 * inch))
|
|
|
|
projets_data = [
|
|
['Field', 'Type', 'Size', 'Constraints', 'Description'],
|
|
['ProjetID', 'AutoNumber', 'Long', 'PRIMARY KEY', 'Unique identifier'],
|
|
['ClientID', 'Long', '—', 'FOREIGN KEY', 'Reference to tbl_Clients.ClientID'],
|
|
['Nom', 'Text', '100', 'NOT NULL', 'Project name'],
|
|
['Description', 'Memo', '—', 'NULL', 'Project description and notes'],
|
|
['TauxHoraire', 'Currency', '—', 'DEFAULT 0, >= 0', 'Hourly rate in EUR'],
|
|
['Actif', 'Yes/No', '—', 'NOT NULL, DEFAULT True', 'Active/archived status'],
|
|
['DateCreation', 'DateTime', '—', 'NOT NULL, DEFAULT Now()', 'Record creation timestamp'],
|
|
]
|
|
|
|
projets_table = Table(projets_data, colWidths=[1.2*inch, 1*inch, 0.7*inch, 1.4*inch, 2.2*inch])
|
|
projets_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), PRIMARY_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 9),
|
|
('FONTSIZE', (0, 1), (-1, -1), 8),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
|
|
('TOPPADDING', (0, 0), (-1, 0), 8),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 6),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 6),
|
|
('TOPPADDING', (0, 1), (-1, -1), 5),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 5),
|
|
]))
|
|
|
|
story.append(projets_table)
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
story.append(Paragraph("<b>Indexes:</b> PRIMARY KEY on ProjetID, FOREIGN KEY on ClientID (with referential integrity), Performance Index on Actif", index_style))
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Table: tbl_Temps
|
|
story.append(Paragraph("3.4 Table: tbl_Temps", styles['SubsectionHeader']))
|
|
story.append(Paragraph("Stores time entry records.", styles['BodyText']))
|
|
story.append(Spacer(1, 0.1 * inch))
|
|
|
|
temps_data = [
|
|
['Field', 'Type', 'Size', 'Constraints', 'Description'],
|
|
['TempsID', 'AutoNumber', 'Long', 'PRIMARY KEY', 'Unique identifier'],
|
|
['ProjetID', 'Long', '—', 'FOREIGN KEY', 'Reference to tbl_Projets.ProjetID'],
|
|
['Date', 'DateTime', '—', 'NOT NULL', 'Entry date (no future dates allowed)'],
|
|
['Duree', 'Double', '—', 'NOT NULL, > 0', 'Duration in hours (decimal)'],
|
|
['Description', 'Memo', '—', 'NULL', 'Work description'],
|
|
['DateCreation', 'DateTime', '—', 'NOT NULL, DEFAULT Now()', 'Record creation timestamp'],
|
|
]
|
|
|
|
temps_table = Table(temps_data, colWidths=[1.2*inch, 1*inch, 0.7*inch, 1.4*inch, 2.2*inch])
|
|
temps_table.setStyle(TableStyle([
|
|
('BACKGROUND', (0, 0), (-1, 0), PRIMARY_COLOR),
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
|
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
|
('FONTSIZE', (0, 0), (-1, 0), 9),
|
|
('FONTSIZE', (0, 1), (-1, -1), 8),
|
|
('BOTTOMPADDING', (0, 0), (-1, 0), 8),
|
|
('TOPPADDING', (0, 0), (-1, 0), 8),
|
|
('GRID', (0, 0), (-1, -1), 0.5, GREY_COLOR),
|
|
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, LIGHT_GREY]),
|
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
|
('LEFTPADDING', (0, 0), (-1, -1), 6),
|
|
('RIGHTPADDING', (0, 0), (-1, -1), 6),
|
|
('TOPPADDING', (0, 1), (-1, -1), 5),
|
|
('BOTTOMPADDING', (0, 1), (-1, -1), 5),
|
|
]))
|
|
|
|
story.append(temps_table)
|
|
story.append(Spacer(1, 0.15 * inch))
|
|
story.append(Paragraph("<b>Indexes:</b> PRIMARY KEY on TempsID, FOREIGN KEY on ProjetID (with referential integrity), Performance Index on Date (for date range queries)", index_style))
|
|
story.append(Spacer(1, 0.2 * inch))
|
|
|
|
# Data Integrity
|
|
story.append(Paragraph("3.5 Data Integrity Rules", styles['SubsectionHeader']))
|
|
|
|
integrity_bullets = [
|
|
"<b>Referential Integrity:</b> Foreign key constraints enforced with configurable CASCADE options",
|
|
"<b>Email Validation:</b> Format validation on tbl_Clients.Email field",
|
|
"<b>Positive Values:</b> CHECK constraints for TauxHoraire and Duree (must be >= 0)",
|
|
"<b>Date Validation:</b> No future dates allowed beyond today's date",
|
|
"<b>Orphan Prevention:</b> All foreign key constraints prevent orphaned records",
|
|
"<b>Audit Trail:</b> All tables include DateCreation timestamps for tracking"
|
|
]
|
|
|
|
for bullet in integrity_bullets:
|
|
story.append(Paragraph(f"• {bullet}", styles['Bullet']))
|
|
|
|
story.append(PageBreak())
|
|
|
|
|
|
# Continue in next part...
|
|
print("Creating professional PDF documentation...")
|
|
print("This script will generate a complete technical reference document.")
|
|
print("")
|
|
|
|
# Create PDF
|
|
doc = SimpleDocTemplate(
|
|
OUTPUT_FILE,
|
|
pagesize=letter,
|
|
rightMargin=0.75*inch,
|
|
leftMargin=0.75*inch,
|
|
topMargin=1*inch,
|
|
bottomMargin=0.75*inch,
|
|
title="TimeTrack Pro - Technical Reference",
|
|
author="Alexis Trouvé"
|
|
)
|
|
|
|
# Create custom styles
|
|
styles = create_styles()
|
|
|
|
# Story container
|
|
story = []
|
|
|
|
# Build document
|
|
print("Creating cover page...")
|
|
create_cover_page(story, styles)
|
|
|
|
print("Creating Section 1: Executive Summary...")
|
|
create_section_1_executive_summary(story, styles)
|
|
|
|
print("Creating Section 2: Project Overview...")
|
|
create_section_2_project_overview(story, styles)
|
|
|
|
print("Creating Section 3: Database Architecture...")
|
|
create_section_3_database(story, styles)
|
|
|
|
print("\nNote: This is Part 1. Continue with additional sections...")
|
|
print("Generating PDF (Part 1)...")
|
|
|
|
# Build PDF with custom canvas
|
|
doc.build(story, canvasmaker=NumberedCanvas)
|
|
|
|
print(f"\n✓ PDF generated successfully!")
|
|
print(f"Location: {OUTPUT_FILE}")
|
|
print(f"\nNote: This is a partial document. Run Part 2 script to add remaining sections.")
|