Add admin-frontend and user-frontend standalone projects, certification/invoice/discovery features, fix auth header and theme consistency

This commit is contained in:
TradeMate Dev
2026-05-22 18:35:30 +08:00
parent 18c6cf5406
commit 52dba37f22
79 changed files with 10333 additions and 248 deletions
+112
View File
@@ -0,0 +1,112 @@
from typing import Optional, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, desc
from app.models.certification import Certification, CertType, CertStatus
from datetime import datetime
import uuid
class CertificationService:
def __init__(self, db: AsyncSession):
self.db = db
async def submit(self, user_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
existing = await self._get_pending(user_id)
if existing:
return {"error": "已有审核中的认证申请,请勿重复提交"}
cert = Certification(
user_id=uuid.UUID(user_id),
cert_type=CertType(data["cert_type"]),
personal_name=data.get("personal_name"),
personal_id=data.get("personal_id"),
company_name=data.get("company_name"),
tax_id=data.get("tax_id"),
business_license_url=data.get("business_license_url"),
status=CertStatus.pending,
)
self.db.add(cert)
await self.db.flush()
return {"id": str(cert.id), "status": cert.status.value}
async def get_user_cert(self, user_id: str) -> Optional[Dict[str, Any]]:
result = await self.db.execute(
select(Certification)
.where(Certification.user_id == uuid.UUID(user_id))
.order_by(desc(Certification.created_at))
.limit(1)
)
cert = result.scalar_one_or_none()
if not cert:
return None
return {
"id": str(cert.id),
"cert_type": cert.cert_type.value,
"personal_name": cert.personal_name,
"personal_id": cert.personal_id,
"company_name": cert.company_name,
"tax_id": cert.tax_id,
"business_license_url": cert.business_license_url,
"status": cert.status.value,
"reject_reason": cert.reject_reason,
"created_at": cert.created_at.isoformat() if cert.created_at else None,
"updated_at": cert.updated_at.isoformat() if cert.updated_at else None,
}
async def list_all(self, page: int, size: int, status: Optional[str] = None) -> Dict[str, Any]:
query = select(Certification).order_by(desc(Certification.created_at))
if status:
query = query.where(Certification.status == CertStatus(status))
offset = (page - 1) * size
result = await self.db.execute(query.offset(offset).limit(size))
certs = result.scalars().all()
total_result = await self.db.execute(
select(Certification).where(Certification.status == CertStatus(status)) if status else select(Certification)
)
total = len(total_result.scalars().all())
return {
"items": [
{
"id": str(c.id),
"user_id": str(c.user_id),
"cert_type": c.cert_type.value,
"personal_name": c.personal_name,
"personal_id": c.personal_id,
"company_name": c.company_name,
"tax_id": c.tax_id,
"status": c.status.value,
"reject_reason": c.reject_reason,
"created_at": c.created_at.isoformat() if c.created_at else None,
}
for c in certs
],
"total": total,
"page": page,
"size": size,
}
async def review(self, cert_id: str, action: str, reason: Optional[str] = None) -> Optional[Dict[str, Any]]:
result = await self.db.execute(
select(Certification).where(Certification.id == uuid.UUID(cert_id))
)
cert = result.scalar_one_or_none()
if not cert:
return None
if action == "approve":
cert.status = CertStatus.approved
else:
cert.status = CertStatus.rejected
cert.reject_reason = reason
await self.db.flush()
return {"id": str(cert.id), "status": cert.status.value}
async def _get_pending(self, user_id: str) -> Optional[Certification]:
result = await self.db.execute(
select(Certification)
.where(
Certification.user_id == uuid.UUID(user_id),
Certification.status == CertStatus.pending,
)
.limit(1)
)
return result.scalar_one_or_none()