Initial certificate system

This commit is contained in:
nelso
2026-06-06 19:32:24 +08:00
commit ce4ed6213b
105 changed files with 9133 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
from app.models.admin import AdminUser, Role
from app.models.certificate import Certificate, CertificateAccessToken, ProjectCourse
from app.models.import_batch import ImportBatch, ImportBatchRow
from app.models.learner import Learner, LearnerNameHistory, LearnerPhoneHistory
from app.models.log import OperationLog
__all__ = [
"AdminUser",
"Certificate",
"CertificateAccessToken",
"ImportBatch",
"ImportBatchRow",
"Learner",
"LearnerNameHistory",
"LearnerPhoneHistory",
"OperationLog",
"ProjectCourse",
"Role",
]

View File

@@ -0,0 +1,27 @@
from datetime import datetime
from sqlalchemy import DateTime, String
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class Role(Base):
__tablename__ = "roles"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(64), unique=True, index=True)
name: Mapped[str] = mapped_column(String(64))
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
class AdminUser(Base):
__tablename__ = "admin_users"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255))
role_code: Mapped[str] = mapped_column(String(64), index=True)
status: Mapped[str] = mapped_column(String(32), default="active")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

View File

@@ -0,0 +1,59 @@
from datetime import date, datetime
from sqlalchemy import Date, DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class ProjectCourse(Base):
__tablename__ = "project_courses"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(16), unique=True, index=True)
name: Mapped[str] = mapped_column(String(128))
default_certificate_name: Mapped[str] = mapped_column(String(128), default="培训结业证书")
default_course_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
default_stage_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
default_issuer_name: Mapped[str] = mapped_column(String(128), default="本公司")
status: Mapped[str] = mapped_column(String(32), default="active")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Certificate(Base):
__tablename__ = "certificates"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
learner_id: Mapped[int] = mapped_column(index=True)
project_code: Mapped[str] = mapped_column(String(16), index=True)
certificate_no: Mapped[str] = mapped_column(String(64), unique=True, index=True)
certificate_name: Mapped[str] = mapped_column(String(128))
class_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
course_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
stage_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
issue_date: Mapped[date] = mapped_column(Date)
issuer_name: Mapped[str] = mapped_column(String(128))
status: Mapped[str] = mapped_column(String(32), default="valid")
pdf_status: Mapped[str] = mapped_column(String(32), default="not_generated")
pdf_file_path: Mapped[str | None] = mapped_column(String(512), nullable=True)
public_token_id: Mapped[int | None] = mapped_column(nullable=True)
qr_token_id: Mapped[int | None] = mapped_column(nullable=True)
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class CertificateAccessToken(Base):
__tablename__ = "certificate_access_tokens"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
certificate_id: Mapped[int] = mapped_column(index=True)
token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True)
token_value: Mapped[str] = mapped_column(String(128), unique=True, index=True)
token_type: Mapped[str] = mapped_column(String(32))
status: Mapped[str] = mapped_column(String(32), default="active")
created_reason: Mapped[str] = mapped_column(String(128), default="initial")
revoked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_access_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

View File

@@ -0,0 +1,35 @@
from datetime import datetime
from sqlalchemy import DateTime, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class ImportBatch(Base):
__tablename__ = "import_batches"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
filename: Mapped[str] = mapped_column(String(255))
file_path: Mapped[str] = mapped_column(String(512))
status: Mapped[str] = mapped_column(String(32), default="uploaded")
total_rows: Mapped[int] = mapped_column(Integer, default=0)
valid_rows: Mapped[int] = mapped_column(Integer, default=0)
failed_rows: Mapped[int] = mapped_column(Integer, default=0)
conflict_rows: Mapped[int] = mapped_column(Integer, default=0)
error_report_path: Mapped[str | None] = mapped_column(String(512), nullable=True)
created_by: Mapped[int | None] = mapped_column(nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class ImportBatchRow(Base):
__tablename__ = "import_batch_rows"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
batch_id: Mapped[int] = mapped_column(index=True)
row_no: Mapped[int] = mapped_column(Integer)
status: Mapped[str] = mapped_column(String(32))
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
raw_json: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

View File

@@ -0,0 +1,44 @@
from datetime import datetime
from sqlalchemy import DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class Learner(Base):
__tablename__ = "learners"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
phone: Mapped[str] = mapped_column(String(32), unique=True, index=True)
current_name: Mapped[str] = mapped_column(String(64), index=True)
id_type: Mapped[str | None] = mapped_column(String(32), nullable=True)
id_no_encrypted: Mapped[str | None] = mapped_column(String(256), nullable=True)
id_no_masked: Mapped[str | None] = mapped_column(String(64), nullable=True)
student_no: Mapped[str | None] = mapped_column(String(64), nullable=True)
status: Mapped[str] = mapped_column(String(32), default="active")
remark: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LearnerNameHistory(Base):
__tablename__ = "learner_name_histories"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
learner_id: Mapped[int] = mapped_column(index=True)
name: Mapped[str] = mapped_column(String(64))
source: Mapped[str] = mapped_column(String(64), default="import")
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
class LearnerPhoneHistory(Base):
__tablename__ = "learner_phone_histories"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
learner_id: Mapped[int] = mapped_column(index=True)
old_phone: Mapped[str] = mapped_column(String(32))
new_phone: Mapped[str] = mapped_column(String(32))
reason: Mapped[str] = mapped_column(String(255))
changed_by: Mapped[int | None] = mapped_column(nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

19
backend/app/models/log.py Normal file
View File

@@ -0,0 +1,19 @@
from datetime import datetime
from sqlalchemy import DateTime, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class OperationLog(Base):
__tablename__ = "operation_logs"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
admin_user_id: Mapped[int | None] = mapped_column(nullable=True, index=True)
action: Mapped[str] = mapped_column(String(64), index=True)
object_type: Mapped[str] = mapped_column(String(64), index=True)
object_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True)
detail_json: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)