from __future__ import annotations from datetime import date, datetime from pydantic import BaseModel, ConfigDict, Field class DataCountItem(BaseModel): code: str label: str count: int class BroadcastMessageCreate(BaseModel): title: str = Field(min_length=1, max_length=100) content: str = Field(min_length=1, max_length=500) attendance_point_names: list[str] = Field(default_factory=list) tone: str = "INFO" status: str = "ACTIVE" starts_at: datetime | None = None ends_at: datetime | None = None sort_no: int = 0 class BroadcastMessageRead(BroadcastMessageCreate): broadcast_id: int created_by: int | str | None = None created_at: datetime updated_at: datetime class AiAssistantConfigUpdate(BaseModel): assistant_name: str = Field(default="嘉恒工艺助手", min_length=1, max_length=100) provider_name: str | None = None api_base_url: str | None = None api_key: str | None = None model_name: str | None = None temperature: float = Field(default=0.3, ge=0, le=2) system_prompt: str | None = None enabled: bool = False class AiAssistantConfigRead(AiAssistantConfigUpdate): config_id: int | None = None api_key_configured: bool = False updated_at: datetime | None = None class SystemConfigUpdate(BaseModel): config_value: str = Field(min_length=1, max_length=500) remark: str | None = Field(default=None, max_length=500) class SystemConfigRead(BaseModel): model_config = ConfigDict(from_attributes=True) config_code: str config_name: str config_value: str remark: str | None = None updated_at: datetime | None = None class SmartOperationReportConfigUpdate(BaseModel): enabled: bool remark: str | None = Field(default=None, max_length=500) class SmartOperationReportConfigRead(BaseModel): config_code: str = "SMART_OPERATION_REPORT_ENABLED" config_name: str = "对接智能报工小程序" enabled: bool = True config_value: str = "开启" remark: str | None = None updated_at: datetime | None = None class LoginUser(BaseModel): user_id: int username: str employee_id: int mobile: str | None = None display_name: str title: str dept_name: str | None = None role_codes: list[str] role_names: list[str] permission_codes: list[str] class AuthLoginRequest(BaseModel): username: str password_ciphertext: str = Field(min_length=1) login_key_id: str = Field(min_length=1) class AuthLoginKeyResponse(BaseModel): key_id: str public_key_pem: str algorithm: str class AuthChangePasswordRequest(BaseModel): old_password: str = Field(min_length=1) new_password: str = Field(min_length=6, max_length=128) class AuthResetPasswordRequest(BaseModel): username: str = Field(min_length=1, max_length=50) new_password: str = Field(min_length=6, max_length=128) class AuthSession(LoginUser): access_token: str token_type: str expires_at: datetime class DepartmentRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int dept_code: str dept_name: str dept_type: str manager_name: str | None = None status: str class EmployeeRead(BaseModel): id: int employee_code: str employee_name: str dept_id: int dept_name: str | None = None mobile: str | None = None gender: str | None = None hire_date: date | None = None job_title: str | None = None shift_code: str | None = None is_operator: bool is_workshop_staff: bool status: str remark: str | None = None username: str | None = None role_names: list[str] = Field(default_factory=list) class EmployeeCreate(BaseModel): employee_name: str dept_id: int = Field(gt=0) mobile: str | None = None gender: str | None = None hire_date: date | None = None job_title: str | None = None shift_code: str | None = None manager_employee_id: int | None = None is_operator: bool = False is_workshop_staff: bool = False status: str = "ACTIVE" remark: str | None = None class EmployeeUpdate(BaseModel): employee_name: str dept_id: int = Field(gt=0) mobile: str | None = None gender: str | None = None hire_date: date | None = None job_title: str | None = None shift_code: str | None = None manager_employee_id: int | None = None is_operator: bool = False is_workshop_staff: bool = False status: str = "ACTIVE" remark: str | None = None class MiniAppPersonnelRead(BaseModel): phone: str name: str role: str | None = None role_name: str | None = None roles: list[str] = Field(default_factory=list) role_names: list[str] = Field(default_factory=list) attendance_point_names: list[str] = Field(default_factory=list) is_temporary: bool = False temporary_expires_at: datetime | None = None temporary_expired: bool = False created_at: datetime | None = None updated_at: datetime | None = None class MiniAppPersonnelUpsert(BaseModel): phone: str = Field(min_length=1, max_length=20) name: str = Field(min_length=1, max_length=64) roles: list[str] = Field(default_factory=lambda: ["worker"]) attendance_point_names: list[str] = Field(default_factory=list) is_temporary: bool = False temporary_expires_at: datetime | None = None class MiniAppAttendancePointRead(BaseModel): name: str latitude: float | None = None longitude: float | None = None radius_meters: int = 500 remark: str | None = None is_active: bool = True created_at: datetime | None = None updated_at: datetime | None = None class MiniAppProductRead(BaseModel): attendance_point_name: str project_no: str product_name: str profile_no: str | None = None material_code: str | None = None material_name: str | None = None supplier: str | None = None product_net_weight_kg: float | None = None product_gross_weight_kg: float | None = None allowed_scrap_rate: float | None = None scrap_loss_rate: float | None = None waste_price_yuan_per_kg: float | None = None device_no: str process_name: str | None = None stamping_method: str | None = None operator_count: float = 1 standard_beat: float = 0 standard_workload: float = 0 display_name: str created_at: datetime | None = None updated_at: datetime | None = None class MiniAppProductUpsert(BaseModel): attendance_point_name: str = Field(min_length=1, max_length=128) project_no: str = Field(min_length=1, max_length=64) product_name: str = Field(min_length=1, max_length=255) profile_no: str | None = None material_code: str | None = None material_name: str | None = None supplier: str | None = None product_net_weight_kg: float | None = Field(default=None, ge=0) product_gross_weight_kg: float | None = Field(default=None, ge=0) allowed_scrap_rate: float | None = Field(default=None, ge=0) scrap_loss_rate: float | None = Field(default=None, ge=0) waste_price_yuan_per_kg: float | None = Field(default=None, ge=0) device_no: str = Field(default="", max_length=64) process_name: str = Field(default="", max_length=128) stamping_method: str | None = None operator_count: float = Field(default=1, ge=0) standard_beat: float = Field(default=0, ge=0) standard_workload: float = Field(default=0, ge=0) original_attendance_point_name: str | None = None original_project_no: str | None = None original_product_name: str | None = None original_device_no: str | None = None original_process_name: str | None = None class MiniAppProductImportResult(BaseModel): imported: int = 0 updated: int = 0 skipped: int = 0 errors: list[str] = Field(default_factory=list) material_sync: dict[str, object] = Field(default_factory=dict) product_specs: dict[str, int] = Field(default_factory=dict) message: str class MiniAppMaterialWastePriceSync(BaseModel): material_code: str | None = None material_name: str | None = None waste_price_yuan_per_kg: float = Field(default=0, ge=0) class WarehouseRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int warehouse_code: str warehouse_name: str warehouse_type: str status: str class ItemRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int item_code: str item_name: str item_type: str specification: str | None = None material_grade: str | None = None unit_weight_kg: float safety_stock_weight_kg: float status: str class MaterialRead(BaseModel): item_id: int item_code: str item_name: str specification: str | None = None material_grade: str | None = None unit_weight_kg: float = 0 safety_stock_weight_kg: float = 0 scrap_sale_price: float = 0 default_supplier_id: int | None = None supplier_name: str | None = None purchase_calc_mode: str min_purchase_qty: float purchase_multiple_qty: float purchase_multiple_weight_kg: float lead_time_days: int quality_rule: str | None = None remark: str | None = None status: str = "ACTIVE" class ProductRead(BaseModel): item_id: int item_code: str item_name: str specification: str | None = None drawing_no: str | None = None product_family: str | None = None version_no: str unit_weight_kg: float allowed_scrap_rate: float standard_output_qty: float = 0 quality_standard: str | None = None safety_stock_weight_kg: float = 0 status: str class ProductCreate(BaseModel): item_code: str | None = None item_name: str specification: str | None = None drawing_no: str | None = None product_family: str | None = None version_no: str = "V1.0" unit_weight_kg: float = Field(gt=0) allowed_scrap_rate: float = Field(default=0, ge=0) standard_output_qty: float = Field(default=0, ge=0) quality_standard: str | None = None safety_stock_weight_kg: float = Field(default=0, ge=0) status: str = "ACTIVE" class ProductUpdate(BaseModel): item_code: str | None = None item_name: str specification: str | None = None drawing_no: str | None = None product_family: str | None = None version_no: str unit_weight_kg: float = Field(gt=0) allowed_scrap_rate: float = Field(default=0, ge=0) standard_output_qty: float = Field(default=0, ge=0) quality_standard: str | None = None safety_stock_weight_kg: float = Field(default=0, ge=0) status: str = "ACTIVE" class MaterialCreate(BaseModel): item_code: str | None = None item_name: str specification: str material_grade: str unit_weight_kg: float = Field(gt=0) safety_stock_weight_kg: float = Field(default=0, ge=0) scrap_sale_price: float = Field(default=0, ge=0) default_supplier_id: int | None = None purchase_calc_mode: str = "BY_WEIGHT" min_purchase_qty: float = Field(default=0, ge=0) purchase_multiple_qty: float = Field(default=0, ge=0) purchase_multiple_weight_kg: float = Field(default=0, ge=0) lead_time_days: int = Field(default=0, ge=0) quality_rule: str | None = None remark: str | None = None status: str = "ACTIVE" class MaterialUpdate(MaterialCreate): pass class BomItemPayload(BaseModel): seq_no: int = Field(gt=0) material_item_id: int = Field(gt=0) usage_type: str = "WEIGHT" qty_per_piece: float = Field(default=1, gt=0) net_weight_per_piece_kg: float = Field(default=0, ge=0) gross_weight_per_piece_kg: float = Field(default=0, ge=0) loss_rate: float = Field(default=0, ge=0) issue_over_tolerance_rate: float = Field(default=0, ge=0) remark: str | None = None class BomItemRead(BomItemPayload): bom_item_id: int material_code: str material_name: str seq_no: int material_item_id: int usage_type: str qty_per_piece: float net_weight_per_piece_kg: float gross_weight_per_piece_kg: float loss_rate: float issue_over_tolerance_rate: float remark: str | None = None class BomRead(BaseModel): bom_id: int bom_code: str product_item_id: int product_code: str product_name: str version_no: str is_default: bool status: str effective_date: date | None = None expire_date: date | None = None remark: str | None = None item_count: int = 0 items: list[BomItemRead] = Field(default_factory=list) class BomUpsert(BaseModel): bom_code: str | None = None product_item_id: int version_no: str = "V1.0" is_default: bool = True status: str = "ACTIVE" effective_date: date | None = None expire_date: date | None = None remark: str | None = None items: list[BomItemPayload] class ProcessRead(BaseModel): process_id: int process_code: str process_name: str process_type: str is_report_required: bool is_quality_gate: bool status: str class RouteOperationPayload(BaseModel): seq_no: int = Field(gt=0) process_id: int = Field(gt=0) work_center_id: int = Field(gt=0) operation_name: str std_setup_minutes: float = Field(default=0, ge=0) std_cycle_seconds: float = Field(default=0, ge=0) std_labor_cost: float = Field(default=0, ge=0) std_machine_cost: float = Field(default=0, ge=0) std_outsource_cost: float = Field(default=0, ge=0) allowed_scrap_rate: float = Field(default=0, ge=0) report_required: bool = True is_key_operation: bool = False status: str = "ACTIVE" remark: str | None = None class RouteOperationRead(RouteOperationPayload): route_operation_id: int process_code: str process_name: str stamping_method: str | None = None work_center_code: str work_center_name: str seq_no: int process_id: int work_center_id: int operation_name: str std_setup_minutes: float std_cycle_seconds: float std_labor_cost: float std_machine_cost: float std_outsource_cost: float allowed_scrap_rate: float report_required: bool is_key_operation: bool status: str remark: str | None = None class ProcessRouteRead(BaseModel): route_id: int route_code: str route_name: str product_item_id: int product_code: str product_name: str version_no: str is_default: bool status: str effective_date: date | None = None expire_date: date | None = None remark: str | None = None operation_count: int = 0 operations: list[RouteOperationRead] = Field(default_factory=list) class ProcessRouteUpsert(BaseModel): route_code: str | None = None route_name: str product_item_id: int version_no: str = "V1.0" is_default: bool = True status: str = "ACTIVE" effective_date: date | None = None expire_date: date | None = None remark: str | None = None operations: list[RouteOperationPayload] class SalesOrderRead(BaseModel): order_id: int order_no: str customer_id: int customer_code: str customer_name: str sales_employee_id: int | None = None sales_employee_name: str | None = None order_date: date promised_date: date | None = None delivery_address: str | None = None total_amount: float status: str line_count: int mrp_count: int = 0 purchase_order_count: int = 0 delivery_count: int = 0 delivery_item_count: int = 0 work_order_count: int = 0 can_delete: bool = False archive_status: str = "未生成" archive_version: int | None = None archive_created_at: datetime | None = None archive_error_message: str | None = None class CustomerRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int customer_code: str customer_name: str short_name: str | None = None contact_name: str | None = None contact_phone: str | None = None contacts: list[dict[str, str]] = Field(default_factory=list, max_length=3) address: str | None = None credit_days: int = 0 status: str class CustomerCreate(BaseModel): customer_name: str short_name: str | None = None contact_name: str | None = None contact_phone: str | None = None contacts: list[dict[str, str]] = Field(default_factory=list, max_length=3) address: str | None = None tax_no: str | None = None credit_days: int = 30 class SalesOrderItemCreate(BaseModel): product_item_id: int order_qty: float unit_price: float promised_date: date | None = None customer_part_no: str | None = None class SalesOrderCreate(BaseModel): customer_id: int sales_employee_id: int | None = None promised_date: date | None = None delivery_address: str | None = None tax_rate: float = 0 remark: str | None = None items: list[SalesOrderItemCreate] class SalesOrderItemRead(BaseModel): sales_order_item_id: int sales_order_id: int order_no: str line_no: int product_item_id: int product_code: str product_name: str customer_part_no: str | None = None order_qty: float delivered_qty: float unit_price: float line_amount: float promised_date: date | None = None status: str class SalesOrderCreateResult(BaseModel): order_id: int order_no: str total_amount: float item_count: int status: str archive_status: str = "未生成" archive_version: int | None = None archive_error_message: str | None = None class MaterialDemandRead(BaseModel): demand_id: int demand_no: str order_no: str material_item_id: int product_code: str product_name: str material_code: str material_name: str purchase_calc_mode: str order_qty: float theoretical_weight_kg: float planned_weight_kg: float shortage_weight_kg: float suggested_purchase_qty: float suggested_purchase_weight_kg: float status: str generated_at: datetime | None = None class MrpGenerationResult(BaseModel): sales_order_id: int order_no: str generated_count: int demand_rows: list[MaterialDemandRead]