from datetime import date, datetime from typing import Any, Dict, List, Optional from pydantic import BaseModel, Field class CustomerBase(BaseModel): name: str = Field(..., description="Customer name") contact_info: Optional[str] = Field(None, description="Contact information") tags: Optional[str] = Field(None, description="Comma-separated tags, e.g. 重点客户,已签约") class CustomerCreate(CustomerBase): pass class CustomerUpdate(BaseModel): name: Optional[str] = None contact_info: Optional[str] = None tags: Optional[str] = None class CustomerRead(CustomerBase): id: int created_at: datetime class Config: from_attributes = True class ProjectRead(BaseModel): id: int customer_id: int raw_requirement: str ai_solution_md: Optional[str] = None status: str created_at: datetime customer: Optional[CustomerRead] = None class Config: from_attributes = True class ProjectUpdate(BaseModel): raw_requirement: Optional[str] = None ai_solution_md: Optional[str] = None status: Optional[str] = None class RequirementAnalyzeRequest(BaseModel): customer_id: int = Field(..., description="Related customer id") raw_text: str = Field(..., description="Raw requirement text from chat or WeChat") class RequirementAnalyzeResponse(BaseModel): project_id: int ai_solution_md: str ai_solution_json: Dict[str, Any] class QuoteGenerateResponse(BaseModel): quote_id: int project_id: int total_amount: float excel_path: str pdf_path: str class ContractGenerateRequest(BaseModel): delivery_date: str = Field(..., description="Delivery date, e.g. 2026-03-31") extra_placeholders: Dict[str, str] = Field( default_factory=dict, description="Additional placeholder mappings for the contract template", ) class ContractGenerateResponse(BaseModel): project_id: int contract_path: str class PushToCloudRequest(BaseModel): platform: str = Field(..., description="feishu | yuque | tencent") title: Optional[str] = Field(None, description="文档标题,默认使用「项目方案 - 项目#id」") body_md: Optional[str] = Field(None, description="要推送的 Markdown 内容,不传则使用项目已保存的方案") class PushToCloudResponse(BaseModel): url: str cloud_doc_id: str class FinanceSyncResult(BaseModel): id: int month: str type: str file_name: str file_path: str class FinanceSyncResponse(BaseModel): status: str = "success" new_files: int = 0 details: List[FinanceSyncResult] = Field(default_factory=list) class FinanceSyncRequest(BaseModel): """ 邮箱附件同步策略: - mode=incremental:默认策略。首次(无历史)全量,否则仅同步 UNSEEN。 - mode=all:同步全部附件(可配合时间范围)。 - mode=latest:只同步「最新一封」邮件中的附件(可配合时间范围)。 时间范围为任意起止日期(含起止日),内部会转为 IMAP 的 SINCE/BEFORE。 """ mode: str = Field("incremental", description="incremental | all | latest") start_date: Optional[date] = Field(None, description="YYYY-MM-DD") end_date: Optional[date] = Field(None, description="YYYY-MM-DD") doc_types: Optional[List[str]] = Field( None, description="要同步的附件类型:invoices/receipts/statements。为空表示默认全部类型。", ) class FinanceRecordRead(BaseModel): id: int month: str type: str file_name: str file_path: str tags: Optional[str] = None meta_json: Optional[str] = None amount: Optional[float] = None billing_date: Optional[date] = None created_at: datetime class Config: from_attributes = True class FinanceRecordUpdate(BaseModel): amount: Optional[float] = None billing_date: Optional[date] = None class FinanceBatchDeleteRequest(BaseModel): ids: List[int] = Field(..., description="要删除的财务记录 ID 列表") class FinanceUploadResponse(BaseModel): id: int month: str type: str file_name: str file_path: str amount: Optional[float] = None billing_date: Optional[date] = None created_at: datetime class Config: from_attributes = True