JhHardwareWRS_BackPoint/app/schemas.py
2026-07-06 11:22:57 +08:00

813 lines
22 KiB
Python

from datetime import date, datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field
from app.models import ReportStatus, Role
class Page(BaseModel):
page: int = 1
page_size: int = 10
total: int = 0
total_pages: int = 1
class PageResponse(BaseModel):
page: int
page_size: int
total: int
total_pages: int
rows: list[Any]
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: "PersonnelOut"
class AttendancePointBase(BaseModel):
name: str = Field(min_length=1, max_length=128)
latitude: float | None = None
longitude: float | None = None
radius_meters: int = Field(default=500, ge=1)
day_start: str = Field(default="08:00", pattern=r"^\d{1,2}:\d{2}$")
day_end: str = Field(default="17:20", pattern=r"^\d{1,2}:\d{2}$")
lunch_start: str = Field(default="11:40", pattern=r"^\d{1,2}:\d{2}$")
lunch_end: str = Field(default="12:40", pattern=r"^\d{1,2}:\d{2}$")
dinner_start: str = Field(default="17:20", pattern=r"^\d{1,2}:\d{2}$")
dinner_end: str = Field(default="18:00", pattern=r"^\d{1,2}:\d{2}$")
overtime_start: str = Field(default="18:00", pattern=r"^\d{1,2}:\d{2}$")
overtime_end: str = Field(default="20:00", pattern=r"^\d{1,2}:\d{2}$")
night_start: str = Field(default="20:00", pattern=r"^\d{1,2}:\d{2}$")
night_end: str = Field(default="06:00", pattern=r"^\d{1,2}:\d{2}$")
remark: str | None = Field(default=None, max_length=500)
is_active: bool = True
class AttendancePointCreate(AttendancePointBase):
original_name: str | None = None
class AttendancePointOut(AttendancePointBase):
created_at: datetime | None = None
updated_at: datetime | None = None
class LoginSelectionResponse(BaseModel):
needs_selection: bool
matched_people: list["PersonnelOut"] = []
selection_token: str | None = None
access_token: str | None = None
token_type: str = "bearer"
user: "PersonnelOut | None" = None
needs_temporary_worker: bool = False
temporary_token: str | None = None
temporary_phone: str | None = None
temporary_expires_at: datetime | None = None
class WechatLoginRequest(BaseModel):
phone_code: str | None = None
phone: str | None = None
phone_candidates: list[str] = []
selected_role: Role | None = None
create_temporary_worker: bool = False
temporary_token: str | None = None
temporary_name: str | None = None
temporary_attendance_point_name: str | None = None
temporary_latitude: float | None = None
temporary_longitude: float | None = None
class RoleSelectionRequest(BaseModel):
selection_token: str
role: Role
class SwitchRoleRequest(BaseModel):
role: Role
class PersonnelBase(BaseModel):
phone: str = Field(min_length=5, max_length=20)
name: str = Field(min_length=1, max_length=64)
role: Role
attendance_point_names: list[str] = []
class PersonnelCreate(PersonnelBase):
original_phone: str | None = None
class PersonnelOut(PersonnelBase):
model_config = ConfigDict(from_attributes=True)
role_name: str
roles: list[Role] = Field(default_factory=list)
role_names: list[str] = Field(default_factory=list)
attendance_points: list[AttendancePointOut] = Field(default_factory=list)
is_temporary: bool = False
temporary_expires_at: datetime | None = None
temporary_expired: bool = False
class ProductBase(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 = None
product_gross_weight_kg: float | None = None
scrap_loss_rate: float | None = None
waste_price_yuan_per_kg: float | None = None
device_no: str = ""
process_name: str = Field(min_length=1, max_length=128)
stamping_method: str | None = None
operator_count: float = 1
process_unit_price_yuan: float = Field(default=0, ge=0)
standard_beat: float = Field(default=0, ge=0)
standard_workload: float | None = None
class ProductCreate(ProductBase):
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 ProductOut(ProductBase):
standard_workload: float
display_name: str
class MoldNameOut(BaseModel):
attendance_point_name: str = ""
name: str
process_name: str = ""
stamping_method: str | None = None
operator_count: float = 1
is_cleaning: bool = False
is_misc: bool = False
is_continuous_die: bool = False
is_multi_person: bool = False
display_name: str = ""
class DeviceQrCreate(BaseModel):
attendance_point_name: str = Field(min_length=1, max_length=128)
product_name: str = Field(min_length=1, max_length=255)
process_name: str = Field(min_length=1, max_length=128)
class DeviceQrBatchCreate(BaseModel):
items: list[DeviceQrCreate] = Field(min_length=1, max_length=500)
class DeviceQrBatchOut(BaseModel):
count: int
file_name: str
zip_url: str
class DeviceQrBatchTaskOut(BaseModel):
id: int
file_name: str
status: str
status_name: str
item_count: int
completed_count: int
failed_count: int = 0
progress_percent: float = 0
zip_url: str | None = None
error_message: str | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
created_at: datetime
updated_at: datetime
class EquipmentBase(BaseModel):
attendance_point_name: str = Field(min_length=1, max_length=128)
device_no: str = Field(min_length=1, max_length=64)
device_type: Literal["冲压设备", "清洗设备"] = "冲压设备"
remark: str | None = Field(default=None, max_length=500)
class EquipmentCreate(EquipmentBase):
original_attendance_point_name: str | None = None
original_device_no: str | None = None
class EquipmentOut(EquipmentBase):
created_at: datetime
updated_at: datetime
class WorkScheduleBase(BaseModel):
day_start: str = Field(pattern=r"^\d{1,2}:\d{2}$")
day_end: str = Field(pattern=r"^\d{1,2}:\d{2}$")
lunch_start: str = Field(pattern=r"^\d{1,2}:\d{2}$")
lunch_end: str = Field(pattern=r"^\d{1,2}:\d{2}$")
dinner_start: str = Field(pattern=r"^\d{1,2}:\d{2}$")
dinner_end: str = Field(pattern=r"^\d{1,2}:\d{2}$")
overtime_start: str = Field(pattern=r"^\d{1,2}:\d{2}$")
overtime_end: str = Field(pattern=r"^\d{1,2}:\d{2}$")
night_start: str = Field(pattern=r"^\d{1,2}:\d{2}$")
night_end: str = Field(pattern=r"^\d{1,2}:\d{2}$")
attendance_latitude: float | None = None
attendance_longitude: float | None = None
attendance_radius_meters: int | None = Field(default=500, ge=1)
auto_submit_hours: float = Field(default=15, ge=1, le=168)
class WorkScheduleUpdate(WorkScheduleBase):
pass
class WorkScheduleSettingsUpdate(WorkScheduleBase):
attendance_points: list[AttendancePointCreate] = Field(default_factory=list)
class WorkScheduleOut(WorkScheduleBase):
id: int
attendance_points: list[AttendancePointOut] = Field(default_factory=list)
updated_by: str | None = None
updated_at: datetime
class ImportResult(BaseModel):
imported: int
updated: int
skipped: int
errors: list[str]
class ClockState(BaseModel):
action: Literal["start", "switch", "switch_or_finish", "finish", "report", "refinish"]
action_text: str
attendance_point_name: str = ""
device_no: str
mold_name: str = ""
process_name: str = ""
stamping_method: str | None = None
display_name: str = ""
is_cleaning: bool = False
is_misc: bool = False
current_mold_name: str = ""
current_process_name: str = ""
session_id: int | None = None
start_at: datetime | None = None
end_at: datetime | None = None
devices: list[str] = []
auto_submit_hours: float = 15
class MoldLockConflictDetail(BaseModel):
code: str = "mold_occupied"
message: str
lock_id: int
attendance_point_name: str = ""
mold_name: str = ""
process_name: str = ""
mold_display_name: str = ""
occupied_phone: str = ""
occupied_name: str = ""
class ClockRequest(BaseModel):
attendance_point_name: str = ""
device_no: str = ""
mold_name: str = ""
process_name: str = ""
latitude: float | None = None
longitude: float | None = None
class ContinueWorkRequest(BaseModel):
session_id: int
class ClockSessionOut(BaseModel):
id: int
attendance_point_name: str = ""
employee_phone: str
start_at: datetime
end_at: datetime | None
status: str
devices: list[str]
class ProductOption(BaseModel):
attendance_point_name: str = ""
project_no: str
product_name: str
material_code: str | None = None
material_name: str | None = None
device_no: str
process_name: str | None = None
stamping_method: str | None = None
operator_count: float = 1
process_unit_price_yuan: float = 0
standard_beat: float
standard_workload: float
is_cleaning: bool = False
is_misc: bool = False
is_continuous_die: bool = False
is_multi_person: bool = False
display_name: str
class EquipmentOption(BaseModel):
attendance_point_name: str = ""
device_no: str
device_type: str = "冲压设备"
remark: str | None = None
display_name: str
class ReportDraftItem(BaseModel):
attendance_point_name: str = ""
device_no: str = ""
project_no: str = ""
product_name: str = ""
material_code: str | None = None
material_name: str | None = None
raw_material_batch_no: str | None = None
raw_material_batch_options: list[str] = []
process_name: str | None = None
stamping_method: str | None = None
operator_count: float = 1
process_unit_price_yuan: float = 0
changeover_count: float = 0
standard_beat: float = 0
standard_workload: float = 0
good_qty: float = 0
defect_qty: float = 0
scrap_qty: float = 0
is_misc: bool = False
is_continuous_die: bool = False
remark: str | None = None
class ReportDraftBlock(BaseModel):
attendance_point_name: str = ""
device_no: str
mold_name: str = ""
process_name: str = ""
stamping_method: str | None = None
display_name: str = ""
start_at: datetime
end_at: datetime
duration_minutes: float = 0
options: list[ProductOption]
equipment_options: list[EquipmentOption] = []
items: list[ReportDraftItem]
is_misc: bool = False
is_continuous_die: bool = False
class ReportDraftOut(BaseModel):
session_id: int
attendance_point_name: str = ""
employee_phone: str
employee_name: str
start_at: datetime
end_at: datetime
duration_minutes: float
device_blocks: list[ReportDraftBlock]
class ReportSubmitItem(BaseModel):
attendance_point_name: str = ""
device_no: str = ""
project_no: str
product_name: str
material_code: str | None = None
material_name: str | None = None
raw_material_batch_no: str | None = None
process_name: str | None = None
stamping_method: str | None = None
operator_count: float = 1
process_unit_price_yuan: float = 0
changeover_count: float = Field(default=0, ge=0)
standard_beat: float
standard_workload: float = 0
good_qty: float = Field(default=0, ge=0)
defect_qty: float = Field(default=0, ge=0)
scrap_qty: float = Field(default=0, ge=0)
started_at: datetime | None = None
is_misc: bool = False
is_continuous_die: bool = False
remark: str | None = None
class ReportSubmitBlock(BaseModel):
attendance_point_name: str = ""
device_no: str
process_name: str = ""
items: list[ReportSubmitItem]
class ReportSubmitRequest(BaseModel):
session_id: int
device_blocks: list[ReportSubmitBlock]
class CleaningReportSubmitItem(BaseModel):
attendance_point_name: str = ""
device_no: str = ""
device_nos: list[str] = Field(default_factory=list)
product_name: str = Field(min_length=1, max_length=255)
process_name: str = Field(min_length=1, max_length=128)
stamping_method: str | None = None
quantity: float = Field(gt=0)
class CleaningReportSubmitRequest(BaseModel):
items: list[CleaningReportSubmitItem]
latitude: float | None = None
longitude: float | None = None
class ReportMetrics(BaseModel):
effective_minutes: float
shift_distribution_text: str = ""
shift_day_minutes: float = 0
shift_overtime_minutes: float = 0
shift_night_minutes: float = 0
shift_other_minutes: float = 0
shift_meal_break_minutes: float = 0
total_good_qty: float
total_output_qty: float
actual_beat: float
standard_beat: float
expected_workload: float
pace_rate: float
workload_rate: float
class ReportItemOut(BaseModel):
id: int | None = None
attendance_point_name: str = ""
device_no: str
project_no: str
product_name: str
material_code: str | None = None
material_name: str | None = None
raw_material_batch_no: str | None = None
process_name: str | None = None
stamping_method: str | None = None
operator_count: float = 1
process_unit_price_yuan: float = 0
changeover_count: float = 0
standard_beat: float
standard_workload: float
good_qty: float
defect_qty: float
scrap_qty: float
allocated_minutes: float = 0
started_at: datetime | None = None
is_misc: bool = False
is_continuous_die: bool = False
is_multi_person: bool = False
remark: str | None = None
corrections: dict[str, Any] = Field(default_factory=dict)
class ReportDeviceSegmentOut(BaseModel):
id: int
attendance_point_name: str = ""
device_no: str
mold_name: str = ""
process_name: str = ""
stamping_method: str | None = None
display_name: str = ""
scanned_at: datetime
released_at: datetime | None = None
sort_order: int = 0
corrections: dict[str, Any] = Field(default_factory=dict)
class ReportOut(BaseModel):
id: int
attendance_point_name: str = ""
session_id: int
employee_phone: str
employee_name: str
report_date: date
start_at: datetime
end_at: datetime
duration_minutes: float
break_minutes: float
status: ReportStatus
status_name: str
reviewer_phone: str | None = None
reviewer_name: str | None = None
reviewed_at: datetime | None = None
reject_reason: str | None = None
review_remark: str | None = None
submitted_at: datetime
is_system_auto_submitted: bool = False
auto_submit_reason: str | None = None
is_multi_person_assistant: bool = False
multi_person_source_report_id: int | None = None
is_voided: bool = False
voided_at: datetime | None = None
voided_by: str | None = None
voided_by_name: str | None = None
unvoid_deadline_at: datetime | None = None
can_void: bool = False
can_unvoid: bool = False
can_edit: bool = False
is_modified: bool = False
device_segments: list[ReportDeviceSegmentOut] = Field(default_factory=list)
items: list[ReportItemOut]
metrics: ReportMetrics
result_text: str
corrections: dict[str, Any] = Field(default_factory=dict)
class ReportItemCorrection(BaseModel):
id: int
device_no: str | None = None
project_no: str | None = None
product_name: str | None = None
process_name: str | None = None
raw_material_batch_no: str | None = None
changeover_count: float | None = Field(default=None, ge=0)
good_qty: float | None = Field(default=None, ge=0)
defect_qty: float | None = Field(default=None, ge=0)
scrap_qty: float | None = Field(default=None, ge=0)
remark: str | None = None
class ReportDeviceCorrection(BaseModel):
id: int
scanned_at: datetime | None = None
class ApproveReportRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
start_at: datetime | None = None
end_at: datetime | None = None
device_corrections: list[ReportDeviceCorrection] = Field(default_factory=list)
item_corrections: list[ReportItemCorrection] = []
review_remark: str | None = Field(default=None, max_length=500)
remark: str | None = None
class RejectReportRequest(BaseModel):
reason: str = "需重新填报"
class DeviceQrOut(BaseModel):
attendance_point_name: str = ""
device_no: str
process_name: str = ""
stamping_method: str | None = None
is_cleaning: bool = False
is_misc: bool = False
is_continuous_die: bool = False
mold_name: str = ""
display_name: str = ""
scene: str
page: str
qr_url: str | None = None
class DashboardRow(BaseModel):
id: str
attendance_point_name: str = ""
report_date: date
employee_phone: str
employee_name: str
project_no: str
product_name: str
process_name: str | None = None
report_count: int
effective_minutes: float
shift_distribution_text: str = ""
shift_day_minutes: float = 0
shift_overtime_minutes: float = 0
shift_night_minutes: float = 0
shift_other_minutes: float = 0
total_good_qty: float
total_defect_qty: float
total_output_qty: float
actual_beat: float
standard_beat: float
expected_workload: float
pace_rate: float
workload_rate: float
pace_text: str
workload_text: str
profile_no: str | None = None
material_code: str | None = None
material_name: str | None = None
raw_material_batch_no: str | None = None
device_no: str | None = None
stamping_method: str | None = None
operator_count: float | None = None
process_unit_price_yuan: float = 0
changeover_count: float = 0
reference_wage: float = 0
worker_type: str = "正式工"
is_multi_person: bool = False
is_multi_person_assistant: bool = False
is_misc: bool = False
is_continuous_die: bool = False
is_system_auto_submitted: bool = False
is_voided: bool = False
review_remark: str | None = None
correction_pairs: list[dict[str, Any]] = Field(default_factory=list)
class UsageStatsRow(BaseModel):
id: str
category: str
attendance_point_name: str = ""
name: str
object_type: str
metric_kind: str
value: float = 0
report_count: int = 0
product_name: str = ""
process_name: str = ""
stamping_method: str = ""
tags: list[str] = Field(default_factory=list)
class UsageStatsDailyRow(BaseModel):
report_date: date
value: float = 0
report_count: int = 0
class UsageStatsReportRow(BaseModel):
report_id: int
report_date: date
employee_phone: str = ""
employee_name: str = ""
display_name: str = ""
value: float = 0
report_count: int = 1
class UsageStatsDetailOut(BaseModel):
id: str = ""
category: str
attendance_point_name: str = ""
name: str = ""
object_type: str = ""
metric_kind: str = ""
value: float = 0
report_count: int = 0
product_name: str = ""
process_name: str = ""
stamping_method: str = ""
tags: list[str] = Field(default_factory=list)
daily_rows: list[UsageStatsDailyRow] = Field(default_factory=list)
report_rows: list[UsageStatsReportRow] = Field(default_factory=list)
class NoticeCreate(BaseModel):
title: str = Field(min_length=1, max_length=120)
content: str = Field(min_length=1, max_length=1000)
attendance_point_names: list[str] = []
sort_order: int = 0
is_active: bool = True
class NoticeUpdate(BaseModel):
title: str | None = Field(default=None, min_length=1, max_length=120)
content: str | None = Field(default=None, min_length=1, max_length=1000)
attendance_point_names: list[str] | None = None
sort_order: int | None = None
is_active: bool | None = None
class NoticeOut(BaseModel):
id: int
title: str
content: str
attendance_point_names: list[str] = []
sort_order: int
is_active: bool
created_by: str
creator_name: str
created_at: datetime
updated_at: datetime
class ReconciliationMonthCell(BaseModel):
month: int
reported_good_qty: float = 0
reconciled_good_qty: float = 0
return_qty: float = 0
class ReconciliationRow(BaseModel):
attendance_point_name: str = ""
product_name: str
months: list[ReconciliationMonthCell]
class ReconciliationLedgerOut(BaseModel):
year: int
current_year: int
current_month: int
rows: list[ReconciliationRow]
class ReconciliationYearsOut(BaseModel):
years: list[int]
current_year: int
class MonitorLockOut(BaseModel):
id: int
attendance_point_name: str = ""
mold_name: str
process_name: str = ""
stamping_method: str | None = None
mold_display_name: str
occupied_phone: str
occupied_name: str
occupied_start_at: datetime
occupied_minutes: float
class MoldLockFeedbackOut(BaseModel):
id: int
attendance_point_name: str = ""
mold_name: str
process_name: str = ""
stamping_method: str | None = None
mold_display_name: str
lock_id: int
reporter_phone: str
reporter_name: str
occupied_phone: str
occupied_name: str
feedback_at: datetime
read_at: datetime | None = None
handled_at: datetime | None = None
status: str
status_name: str
can_release: bool = True
class MoldLockFeedbackCreate(BaseModel):
lock_id: int
class ReconciliationEntryUpdate(BaseModel):
attendance_point_name: str = Field(min_length=1, max_length=128)
year: int = Field(ge=2000, le=2100)
month: int = Field(ge=1, le=12)
product_name: str = Field(min_length=1, max_length=255)
reconciled_good_qty: float | None = Field(default=None, ge=0)
return_qty: float | None = Field(default=None, ge=0)
class ErpLoginQrcodeRequest(BaseModel):
ticket: str = Field(min_length=1, max_length=16)
session_id: int = Field(ge=1)
class ErpLoginQrcodeResponse(BaseModel):
qr_url: str | None = None
page: str
scene: str
class ErpLoginPreviewResponse(BaseModel):
session_id: int
system_name: str
started_at: datetime
expires_at: datetime
request_ip_hint: str | None = None
user_agent_hint: str | None = None
class ErpLoginConfirmRequest(BaseModel):
phone_code: str = Field(min_length=1, max_length=256)
class ErpLoginActionResponse(BaseModel):
status: str
failure_reason: str | None = None
PersonnelOut.model_rebuild()
TokenResponse.model_rebuild()
LoginSelectionResponse.model_rebuild()