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

@@ -28,21 +28,39 @@ def generate_system_code(prefix: str, meeting_date: date, sequence: int) -> str:
def get_next_sequence(cursor, prefix: str, date_str: str) -> int:
"""Get next sequence number for a given prefix and date."""
pattern = f"{prefix}-{date_str}-%"
cursor.execute(
"""
SELECT system_code FROM meeting_conclusions WHERE system_code LIKE %s
UNION
SELECT system_code FROM meeting_action_items WHERE system_code LIKE %s
ORDER BY system_code DESC LIMIT 1
""",
(pattern, pattern),
)
result = cursor.fetchone()
if result:
last_code = result["system_code"]
last_seq = int(last_code.split("-")[-1])
return last_seq + 1
return 1
if prefix == "M":
# For meeting numbers, search in meeting_records table
cursor.execute(
"""
SELECT meeting_number FROM meeting_records WHERE meeting_number LIKE %s
ORDER BY meeting_number DESC LIMIT 1
""",
(pattern,),
)
result = cursor.fetchone()
if result and result.get("meeting_number"):
last_code = result["meeting_number"]
last_seq = int(last_code.split("-")[-1])
return last_seq + 1
return 1
else:
# For conclusions (C) and action items (A)
cursor.execute(
"""
SELECT system_code FROM meeting_conclusions WHERE system_code LIKE %s
UNION
SELECT system_code FROM meeting_action_items WHERE system_code LIKE %s
ORDER BY system_code DESC LIMIT 1
""",
(pattern, pattern),
)
result = cursor.fetchone()
if result:
last_code = result["system_code"]
last_seq = int(last_code.split("-")[-1])
return last_seq + 1
return 1
@router.post("/meetings", response_model=MeetingResponse)
@@ -56,15 +74,20 @@ async def create_meeting(
date_str = meeting_date.strftime("%Y%m%d")
with get_db_cursor(commit=True) as cursor:
# Generate meeting number
meeting_seq = get_next_sequence(cursor, "M", date_str)
meeting_number = generate_system_code("M", meeting_date, meeting_seq)
# Insert meeting record
cursor.execute(
"""
INSERT INTO meeting_records
(uuid, subject, meeting_time, location, chairperson, recorder, attendees, transcript_blob, created_by)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
(uuid, meeting_number, subject, meeting_time, location, chairperson, recorder, attendees, transcript_blob, created_by)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""",
(
meeting_uuid,
meeting_number,
meeting.subject,
meeting.meeting_time,
meeting.location,
@@ -133,6 +156,7 @@ async def create_meeting(
return MeetingResponse(
meeting_id=record["meeting_id"],
uuid=record["uuid"],
meeting_number=record.get("meeting_number"),
subject=record["subject"],
meeting_time=record["meeting_time"],
location=record["location"],
@@ -215,6 +239,7 @@ async def get_meeting(
return MeetingResponse(
meeting_id=record["meeting_id"],
uuid=record["uuid"],
meeting_number=record.get("meeting_number"),
subject=record["subject"],
meeting_time=record["meeting_time"],
location=record["location"],