feat: Excel template export with meeting number auto-generation

- Add meeting_number field (M-YYYYMMDD-XX format) with auto-generation
- Refactor Excel export to use cell coordinates instead of placeholders
- Export files saved to backend/record/ directory with meeting number filename
- Add database migration for meeting_number column
- Add start.sh script for managing frontend/backend/sidecar services
- Update OpenSpec documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-11 19:45:53 +08:00
parent a4a2fc3ae7
commit e790f48967
16 changed files with 1139 additions and 122 deletions

View File

@@ -0,0 +1,119 @@
"""Test Excel export functionality with template filling."""
import os
import sys
from datetime import datetime, date
from openpyxl import load_workbook
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.routers.export import fill_template_workbook, create_default_workbook, TEMPLATE_DIR, RECORD_DIR
def test_excel_export():
"""Test Excel generation with mock data."""
# Mock meeting data
meeting = {
"meeting_id": 1,
"uuid": "test-uuid-123",
"meeting_number": "M-20251211-01",
"subject": "專案進度討論會議",
"meeting_time": datetime(2025, 12, 11, 14, 30),
"location": "會議室A",
"chairperson": "王經理",
"recorder": "李小明",
"attendees": "張三, 李四, 王五",
"created_by": "test@example.com",
}
# Mock conclusions
conclusions = [
{"conclusion_id": 1, "content": "確認專案時程延後兩週", "system_code": "C-20251211-01"},
{"conclusion_id": 2, "content": "同意增加測試人力", "system_code": "C-20251211-02"},
{"conclusion_id": 3, "content": "下次會議改為線上進行", "system_code": "C-20251211-03"},
]
# Mock action items
actions = [
{
"action_id": 1,
"content": "更新專案時程表",
"owner": "張三",
"due_date": date(2025, 12, 15),
"status": "Open",
"system_code": "A-20251211-01",
},
{
"action_id": 2,
"content": "聯繫人資部門增聘測試人員",
"owner": "李四",
"due_date": date(2025, 12, 20),
"status": "In Progress",
"system_code": "A-20251211-02",
},
{
"action_id": 3,
"content": "準備線上會議設備",
"owner": "王五",
"due_date": None,
"status": "Open",
"system_code": "A-20251211-03",
},
]
# Check paths
template_path = os.path.join(TEMPLATE_DIR, "meeting_template.xlsx")
print(f"Template directory: {TEMPLATE_DIR}")
print(f"Record directory: {RECORD_DIR}")
print(f"Template exists: {os.path.exists(template_path)}")
# Ensure record directory exists
os.makedirs(RECORD_DIR, exist_ok=True)
# Generate filename with meeting number
meeting_number = meeting.get("meeting_number", "")
filename = f"{meeting_number}.xlsx" if meeting_number else f"meeting_{meeting['uuid']}.xlsx"
output_path = os.path.join(RECORD_DIR, filename)
# Test with template if exists
if os.path.exists(template_path):
print("\n=== Testing with template ===")
wb = load_workbook(template_path)
wb = fill_template_workbook(wb, meeting, conclusions, actions)
wb.save(output_path)
print(f"Saved to: {output_path}")
# Verify cell values
ws = wb.active
print("\n--- Verification ---")
print(f"D3 (Subject): {ws['D3'].value}")
print(f"D4 (Time): {ws['D4'].value}")
print(f"D5 (Chair): {ws['D5'].value}")
print(f"F4 (Location): {ws['F4'].value}")
print(f"F5 (Recorder): {ws['F5'].value}")
print(f"D6 (Attendees): {ws['D6'].value}")
print(f"C8 (Meeting Number): {ws['C8'].value}")
print(f"D8 (Conclusions): {ws['D8'].value}")
print(f"\nAction Items:")
for i in range(3):
row = 10 + i
print(f" Row {row}: C={ws[f'C{row}'].value}, D={ws[f'D{row}'].value}, F={ws[f'F{row}'].value}, G={ws[f'G{row}'].value}, H={ws[f'H{row}'].value}")
else:
print("\n=== Template not found, using default generation ===")
wb = create_default_workbook(meeting, conclusions, actions)
wb.save(output_path)
print(f"Saved to: {output_path}")
print(f"\n✅ Test completed! File saved to: {output_path}")
# List files in record directory
print(f"\n--- Files in record directory ---")
for f in os.listdir(RECORD_DIR):
print(f" {f}")
return True
if __name__ == "__main__":
test_excel_export()