from __future__ import annotations from datetime import date, datetime from decimal import Decimal from pydantic import BaseModel, ConfigDict, Field class ContactPayload(BaseModel): contact_name: str | None = None contact_phone: str | None = None class SupplierRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int supplier_code: str supplier_name: str short_name: str | None = None contact_name: str | None = None contact_phone: str | None = None contacts: list[ContactPayload] = Field(default_factory=list, max_length=3) address: str | None = None lead_time_days: int default_tax_rate: float status: str remark: str | None = None class SupplierCreate(BaseModel): supplier_name: str short_name: str | None = None contact_name: str | None = None contact_phone: str | None = None contacts: list[ContactPayload] = Field(default_factory=list, max_length=3) address: str | None = None lead_time_days: int = 0 default_tax_rate: float = 0 status: str = "ACTIVE" remark: str | None = None class SupplierUpdate(SupplierCreate): pass class WorkCenterRead(BaseModel): id: int center_code: str center_name: str dept_id: int dept_name: str | None = None center_type: str shift_pattern: str | None = None capacity_hours_per_day: float status: str class WarehouseLocationRead(BaseModel): id: int warehouse_id: int warehouse_code: str warehouse_name: str location_code: str location_name: str zone_name: str | None = None is_default: bool is_locked: bool status: str class PurchaseOrderItemCreate(BaseModel): material_item_id: int source_demand_id: int | None = None order_qty: float = 0 order_weight_kg: float = 0 unit_price: float = 0 remark: str | None = None class PurchaseOrderCreate(BaseModel): supplier_id: int order_date: date | None = None expected_date: date | None = None warning_lead_days: int = Field(default=0, ge=0) purchaser_employee_id: int | None = None target_warehouse_type: str = "RAW" sales_order_ids: list[int] = Field(default_factory=list) tax_rate: float = 0 remark: str | None = None items: list[PurchaseOrderItemCreate] class PurchaseOrderUpdate(PurchaseOrderCreate): pass class PurchaseOrderRead(BaseModel): purchase_order_id: int po_no: str source_type: str = "STOCKING" source_order_nos: str | None = None sales_order_ids: list[int] = Field(default_factory=list) supplier_id: int supplier_code: str supplier_name: str order_date: date expected_date: date | None = None warning_lead_days: int = 0 warning_start_date: date | None = None is_warning_active: bool = False purchaser_employee_id: int | None = None purchaser_name: str | None = None target_warehouse_type: str = "RAW" tax_rate: float total_amount: float status: str display_status: str | None = None remark: str | None = None line_count: int total_order_qty: float = 0 total_order_weight_kg: float total_received_qty: float = 0 total_received_weight_kg: float has_return: bool = False total_return_weight_kg: float = 0 archive_status: str = "未生成" archive_version: int | None = None archive_created_at: datetime | None = None archive_error_message: str | None = None class PurchaseOrderItemRead(BaseModel): purchase_order_item_id: int purchase_order_id: int po_no: str line_no: int material_item_id: int material_code: str material_name: str specification: str | None = None material_grade: str | None = None performance_requirement: str | None = None purchase_calc_mode: str | None = None source_demand_id: int | None = None order_qty: float order_weight_kg: float received_qty: float received_weight_kg: float returned_weight_kg: float = 0 net_received_weight_kg: float = 0 has_return: bool = False unit_price: float line_amount: float status: str display_status: str | None = None remark: str | None = None class PurchaseReceiptItemCreate(BaseModel): purchase_order_item_id: int material_item_id: int location_id: int | None = None lot_no: str | None = None received_qty: float = 0 received_weight_kg: float = 0 unit_cost: float = 0 remark: str | None = None class PurchaseReceiptCreate(BaseModel): purchase_order_id: int warehouse_id: int receipt_date: datetime | None = None receiver_employee_id: int | None = None supplier_delivery_no: str | None = None waybill_no: str | None = None order_photo_url: str | None = None freight_amount: float | None = None remark: str | None = None items: list[PurchaseReceiptItemCreate] class PurchaseReceiptRead(BaseModel): receipt_id: int receipt_no: str purchase_order_id: int po_no: str supplier_id: int supplier_name: str warehouse_id: int warehouse_name: str target_warehouse_type: str = "RAW" receipt_date: datetime receiver_employee_id: int | None = None receiver_name: str | None = None supplier_delivery_no: str | None = None logistics_waybill_no: str | None = None logistics_freight_amount: float | None = None logistics_photo_url: str | None = None material_names: str | None = None status: str remark: str | None = None line_count: int total_received_qty: float = 0 total_received_weight_kg: float archive_status: str = "未生成" archive_version: int | None = None archive_created_at: datetime | None = None archive_error_message: str | None = None class PurchaseReceiptItemRead(BaseModel): receipt_item_id: int receipt_id: int receipt_no: str line_no: int purchase_order_item_id: int material_item_id: int material_code: str material_name: str warehouse_id: int warehouse_name: str location_id: int | None = None location_name: str | None = None lot_no: str received_qty: float received_weight_kg: float accepted_qty: float accepted_weight_kg: float unit_cost: float status: str quality_status: str | None = None remark: str | None = None class StockLotPurchaseLinkRead(BaseModel): purchase_order_id: int po_no: str purchase_order_item_id: int material_item_id: int material_code: str material_name: str lot_id: int lot_no: str accepted_weight_kg: float returned_weight_kg: float returnable_weight_kg: float unit_cost: float class RawMaterialReturnOutboundItemCreate(BaseModel): lot_id: int purchase_order_item_id: int return_weight_kg: float reason: str | None = None remark: str | None = None class RawMaterialReturnOutboundCreate(BaseModel): warehouse_id: int return_date: datetime | None = None waybill_no: str | None = None order_photo_url: str | None = None freight_amount: float | None = None reason: str | None = None remark: str | None = None items: list[RawMaterialReturnOutboundItemCreate] class PurchaseReturnItemRead(BaseModel): purchase_return_item_id: int purchase_return_id: int line_no: int purchase_order_item_id: int material_item_id: int material_code: str material_name: str lot_id: int lot_no: str return_weight_kg: float unit_cost: float amount: float reason: str | None = None status: str remark: str | None = None class PurchaseReturnRead(BaseModel): purchase_return_id: int return_no: str purchase_order_id: int po_no: str warehouse_id: int warehouse_name: str return_date: datetime logistics_waybill_no: str | None = None logistics_freight_amount: float | None = None logistics_photo_url: str | None = None reason: str | None = None status: str remark: str | None = None total_return_weight_kg: float total_amount: float items: list[PurchaseReturnItemRead] = Field(default_factory=list) archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class StockLotRead(BaseModel): lot_id: int lot_no: str parent_lot_id: int | None = None lot_role: str | None = None material_sub_batch_no: str | None = None item_id: int item_code: str item_name: str item_type: str specification: str | None = None item_remark: str | None = None stock_unit_name: str | None = None purchase_unit_name: str | None = None sales_unit_name: str | None = None warehouse_id: int warehouse_name: str warehouse_type: str | None = None location_id: int | None = None location_name: str | None = None source_doc_type: str source_doc_id: int source_line_id: int | None = None source_receipt_no: str | None = None source_purchase_order_no: str | None = None source_material_lot_id: int | None = None source_material_lot_no: str | None = None source_material_sub_batch_no: str | None = None source_material_summary: str | None = None logistics_waybill_no: str | None = None logistics_freight_amount: float | None = None logistics_photo_url: str | None = None inbound_qty: float inbound_weight_kg: float remaining_qty: float remaining_weight_kg: float locked_qty: float locked_weight_kg: float unit_cost: float quality_status: str quality_user_name: str | None = None status: str created_at: datetime archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class StockLotOptionRead(BaseModel): lot_id: int lot_no: str item_id: int item_code: str item_name: str warehouse_id: int warehouse_name: str remaining_weight_kg: float unit_cost: float quality_status: str status: str is_available_for_issue: bool disabled_reason: str | None = None class StockBalanceRead(BaseModel): stock_balance_id: int item_id: int item_code: str item_name: str item_type: str specification: str | None = None item_remark: str | None = None stock_unit_name: str | None = None purchase_unit_name: str | None = None sales_unit_name: str | None = None warehouse_id: int warehouse_name: str warehouse_type: str | None = None location_id: int | None = None location_name: str | None = None qty_on_hand: float weight_on_hand_kg: float qty_available: float weight_available_kg: float qty_allocated: float weight_allocated_kg: float avg_unit_cost: float updated_at: datetime class InventoryTxnRead(BaseModel): inventory_txn_id: int txn_no: str txn_type: str item_id: int item_code: str item_name: str warehouse_id: int warehouse_name: str warehouse_type: str | None = None location_id: int | None = None location_name: str | None = None lot_id: int | None = None lot_no: str | None = None qty_change: float weight_change_kg: float unit_cost: float amount: float source_doc_type: str source_doc_id: int source_line_id: int | None = None source_material_lot_id: int | None = None source_material_lot_no: str | None = None source_material_sub_batch_no: str | None = None source_material_summary: str | None = None biz_time: datetime logistics_waybill_no: str | None = None logistics_freight_amount: float | None = None logistics_photo_url: str | None = None remark: str | None = None archive_status: str = "未生成" archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class InventoryTxnLedgerRowRead(InventoryTxnRead): direction: str source_line_id: int | None = None operator_user_id: int | None = None operator_name: str | None = None class InventoryTxnLedgerSummaryRead(BaseModel): in_count: int = 0 out_count: int = 0 adjust_count: int = 0 in_qty: float = 0 out_qty: float = 0 in_weight_kg: float = 0 out_weight_kg: float = 0 latest_biz_time: datetime | None = None class InventoryTxnLedgerResponse(BaseModel): items: list[InventoryTxnLedgerRowRead] total: int summary: InventoryTxnLedgerSummaryRead class SelectedStockLotIssueCreate(BaseModel): source_lot_id: int issued_weight_kg: float material_item_id: int | None = None class WorkOrderCreate(BaseModel): source_sales_order_item_id: int | None = None product_item_id: int planned_qty: float = 0 issue_weight_kg: float = 0 selected_stock_lots: list[SelectedStockLotIssueCreate] = Field(default_factory=list) planned_start_time: datetime | None = None planned_end_time: datetime | None = None priority_level: int = 3 auto_issue_materials: bool = False remark: str | None = None class WorkOrderRead(BaseModel): work_order_id: int work_order_no: str work_order_type: str source_sales_order_item_id: int | None = None source_order_no: str | None = None product_item_id: int product_code: str product_name: str product_version_no: str | None = None route_id: int route_name: str bom_id: int planned_qty: float released_qty: float finished_qty: float scrap_qty: float planned_start_time: datetime | None = None planned_end_time: datetime | None = None actual_start_time: datetime | None = None actual_end_time: datetime | None = None priority_level: int status: str remark: str | None = None operation_count: int abnormal_operation_count: int reported_good_qty: float = 0 received_qty: float = 0 max_receivable_qty: float = 0 all_operations_done: bool = False archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class WorkOrderMaterialRead(BaseModel): work_order_material_id: int work_order_id: int work_order_no: str bom_item_id: int material_item_id: int material_code: str material_name: str required_qty: float required_weight_kg: float issued_qty: float issued_weight_kg: float returned_qty: float returned_weight_kg: float material_sub_batch_summary: str | None = None issue_amount: float = 0 status: str remark: str | None = None class WorkOrderMaterialIssueRead(BaseModel): material_issue_id: int work_order_material_id: int work_order_id: int work_order_no: str material_item_id: int material_code: str material_name: str source_lot_id: int source_lot_no: str material_sub_batch_no: str issued_qty: float issued_weight_kg: float unit_cost: float amount: float issue_time: datetime remark: str | None = None class ProductionBatchLedgerTxnRead(BaseModel): production_ledger_txn_id: int production_ledger_id: int txn_type: str qty_delta: float = 0 weight_delta_kg: float = 0 outside_weight_after_kg: float = 0 source_doc_type: str | None = None source_doc_id: int | None = None source_line_id: int | None = None biz_time: datetime operator_user_id: int | None = None remark: str | None = None archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class ProductionBatchLedgerRead(BaseModel): production_ledger_id: int material_lot_id: int material_lot_no: str material_item_id: int material_code: str | None = None material_name: str | None = None product_item_id: int product_code: str | None = None product_name: str | None = None status: str miniapp_selectable: bool = True total_issued_weight_kg: float = 0 outside_weight_kg: float = 0 finished_inbound_qty: float = 0 surplus_inbound_weight_kg: float = 0 scrap_inbound_weight_kg: float = 0 writeoff_weight_kg: float = 0 first_issue_time: datetime | None = None last_issue_time: datetime | None = None locked_at: datetime | None = None reopened_at: datetime | None = None lock_count: int = 0 remark: str | None = None material_out_archive_status: str | None = None material_out_archive_business_id: int | None = None material_out_archive_document_type: str | None = None material_out_archive_error_message: str | None = None txns: list[ProductionBatchLedgerTxnRead] = Field(default_factory=list) class ProductionBatchLedgerInboundPreviewRead(BaseModel): production_ledger_id: int material_lot_no: str product_item_id: int product_code: str | None = None product_name: str | None = None material_item_id: int material_code: str | None = None material_name: str | None = None gross_weight_per_piece_kg: float = 0 net_weight_per_piece_kg: float = 0 total_issued_weight_kg: float = 0 outside_weight_kg: float = 0 finished_inbound_qty: float = 0 surplus_inbound_weight_kg: float = 0 scrap_inbound_weight_kg: float = 0 writeoff_weight_kg: float = 0 reference_finished_qty: float = 0 used_material_weight_kg: float = 0 theoretical_surplus_weight_kg: float = 0 theoretical_scrap_weight_kg: float = 0 class ProductionBatchLedgerInboundCreate(BaseModel): production_ledger_id: int finished_qty: float = 0 surplus_weight_kg: float = 0 scrap_weight_kg: float = 0 lock_material_batch: bool = False lock_confirmed: bool = False deviation_remark: str | None = None remark: str | None = None class ProductionBatchLedgerInboundRead(BaseModel): production_ledger_id: int material_lot_no: str finished_qty: float = 0 surplus_weight_kg: float = 0 scrap_weight_kg: float = 0 finished_lot_id: int | None = None surplus_lot_id: int | None = None scrap_lot_id: int | None = None finished_txn_id: int | None = None surplus_txn_id: int | None = None scrap_txn_id: int | None = None locked: bool = False status: str remark: str | None = None archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class WorkOrderOperationRead(BaseModel): work_order_operation_id: int work_order_id: int work_order_no: str route_operation_id: int seq_no: int operation_name: str work_center_id: int work_center_name: str planned_qty: float report_qty: float good_qty: float scrap_qty: float rework_qty: float std_labor_cost: float std_machine_cost: float std_outsource_cost: float allowed_scrap_rate: float is_abnormal: bool status: str remark: str | None = None class WorkOrderLedgerMaterialRead(BaseModel): material_item_id: int material_code: str material_name: str source_lot_id: int source_lot_no: str issued_weight_kg: float returned_weight_kg: float = 0 unit_cost: float = 0 amount: float = 0 class WorkOrderLedgerOperationRead(BaseModel): work_order_operation_id: int seq_no: int operation_name: str report_qty: float good_qty: float scrap_qty: float status: str class WorkOrderLedgerLimitRead(BaseModel): gross_weight_per_piece_kg: float net_weight_per_piece_kg: float issued_weight_kg: float used_material_weight_kg: float returned_material_weight_kg: float final_operation_good_qty: float finished_received_qty: float total_operation_scrap_qty: float scrap_inbound_weight_kg: float max_finished_inbound_qty: float max_scrap_inbound_weight_kg: float max_surplus_inbound_weight_kg: float tolerance_finished_inbound_qty: float tolerance_scrap_inbound_weight_kg: float tolerance_surplus_inbound_weight_kg: float class WorkOrderLedgerRead(BaseModel): work_order_id: int work_order_no: str product_item_id: int product_code: str product_name: str planned_qty: float status: str remark: str | None = None material_summary: str source_lot_summary: str issued_weight_kg: float used_material_weight_kg: float returned_material_weight_kg: float finished_received_qty: float max_finished_inbound_qty: float max_scrap_inbound_weight_kg: float max_surplus_inbound_weight_kg: float limits: WorkOrderLedgerLimitRead materials: list[WorkOrderLedgerMaterialRead] operations: list[WorkOrderLedgerOperationRead] class ProductionWorkOrderInboundPreviewRead(BaseModel): work_order_id: int work_order_no: str product_item_id: int product_code: str product_name: str source_lot_id: int source_lot_no: str material_item_id: int material_code: str material_name: str gross_weight_per_piece_kg: float net_weight_per_piece_kg: float issued_weight_kg: float returned_weight_kg: float final_operation_good_qty: float finished_inbound_qty: float reported_scrap_qty: float scrap_inbound_weight_kg: float reference_finished_qty: float remaining_reference_finished_qty: float finished_qty_upper_limit: float finished_qty: float theoretical_surplus_weight_kg: float theoretical_scrap_weight_kg: float class ProductionWorkOrderInboundCreate(BaseModel): work_order_id: int finished_qty: float = 0 surplus_weight_kg: float = 0 scrap_weight_kg: float = 0 settle_work_order: bool = False settle_confirmed: bool = False deviation_remark: str | None = None remark: str | None = None class ProductionWorkOrderInboundRead(BaseModel): work_order_id: int work_order_no: str finished_qty: float = 0 surplus_weight_kg: float = 0 scrap_weight_kg: float = 0 finished_lot_id: int | None = None surplus_lot_id: int | None = None scrap_lot_id: int | None = None finished_txn_id: int | None = None surplus_txn_id: int | None = None scrap_txn_id: int | None = None settled: bool = False status: str remark: str | None = None archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class OperationReportCreate(BaseModel): work_order_id: int work_order_operation_id: int employee_id: int equipment_id: int | None = None report_source: str = "MANUAL" shift_code: str | None = None start_time: datetime end_time: datetime report_qty: float good_qty: float scrap_qty: float = 0 rework_qty: float = 0 scrap_reason: str | None = None downtime_minutes: float = 0 extra_cost_amount: float = 0 remark: str | None = None class OperationReportRead(BaseModel): report_id: int report_no: str work_order_id: int work_order_no: str work_order_operation_id: int seq_no: int operation_name: str employee_id: int employee_name: str equipment_id: int | None = None equipment_name: str | None = None report_source: str shift_code: str | None = None start_time: datetime end_time: datetime report_qty: float good_qty: float scrap_qty: float rework_qty: float downtime_minutes: float extra_cost_amount: float is_abnormal: bool ref_cycle_seconds: float = 0 actual_cycle_seconds: float | None = None abnormal_reason: str | None = None scrap_reason: str | None = None remark: str | None = None class MiniAppOperationReportRead(BaseModel): source_report_id: int source_item_id: int report_no: str attendance_point_name: str = "" employee_phone: str employee_name: str report_date: date start_time: datetime end_time: datetime duration_minutes: float = 0 effective_minutes: float = 0 status: str status_name: str reviewer_phone: str | None = None reviewed_at: datetime | None = None reject_reason: str | None = None 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 standard_beat: float = 0 standard_workload: float = 0 good_qty: float = 0 defect_qty: float = 0 scrap_qty: float = 0 erp_scrap_qty: float = 0 allocated_minutes: float = 0 started_at: datetime | None = None work_order_id: int | None = None work_order_no: str | None = None work_order_operation_id: int | None = None operation_name: str | None = None erp_report_id: int | None = None erp_report_no: str | None = None is_synced_to_erp: bool = False abnormal_reason: str | None = None class MiniAppOperationReportSyncRead(BaseModel): sync_from: datetime | None = None sync_to: datetime last_synced_at: datetime total: int = 0 synced: int = 0 skipped: int = 0 failed: int = 0 class MiniAppOperationReportSyncStateRead(BaseModel): last_synced_at: datetime | None = None class MiniAppScrapRecordCreate(BaseModel): source_item_id: int scrap_qty: float = 0 scrap_weight_kg: float = 0 scrap_reason: str | None = None saleable_scrap_weight_kg: float = 0 scrap_cost_amount: float = 0 class ScrapRecordCreate(BaseModel): report_id: int work_order_id: int work_order_operation_id: int item_id: int scrap_qty: float scrap_weight_kg: float = 0 scrap_reason: str | None = None disposal_type: str = "SCRAP" saleable_scrap_weight_kg: float = 0 scrap_cost_amount: float = 0 class ScrapRecordRead(BaseModel): scrap_record_id: int scrap_no: str report_id: int report_no: str work_order_id: int work_order_no: str work_order_operation_id: int operation_name: str item_id: int item_code: str item_name: str scrap_qty: float scrap_weight_kg: float scrap_reason: str | None = None disposal_type: str saleable_scrap_weight_kg: float scrap_cost_amount: float status: str created_at: datetime class CompletionReceiptItemCreate(BaseModel): product_item_id: int location_id: int | None = None receipt_qty: float receipt_weight_kg: float = 0 remark: str | None = None class CompletionReceiptCreate(BaseModel): work_order_id: int warehouse_id: int receipt_time: datetime | None = None receiver_employee_id: int | None = None remark: str | None = None settle_work_order: bool = False settle_remark: str | None = None items: list[CompletionReceiptItemCreate] class CompletionReceiptRead(BaseModel): completion_receipt_id: int receipt_no: str work_order_id: int work_order_no: str product_item_id: int product_code: str product_name: str warehouse_id: int warehouse_name: str receipt_time: datetime receiver_employee_id: int | None = None receiver_name: str | None = None status: str line_count: int total_receipt_qty: float total_receipt_weight_kg: float remark: str | None = None class CompletionReceiptItemRead(BaseModel): completion_receipt_item_id: int completion_receipt_id: int receipt_no: str line_no: int product_item_id: int product_code: str product_name: str warehouse_id: int warehouse_name: str location_id: int | None = None location_name: str | None = None lot_no: str source_material_lot_id: int | None = None source_material_lot_no: str | None = None source_material_sub_batch_no: str | None = None source_material_summary: str | None = None receipt_qty: float receipt_weight_kg: float unit_cost: float status: str remark: str | None = None class DeliveryItemCreate(BaseModel): sales_order_item_id: int | None = None product_item_id: int lot_id: int | None = None delivery_qty: float delivery_weight_kg: float = 0 unit_price: float = 0 remark: str | None = None class DeliveryCreate(BaseModel): sales_order_id: int | None = None customer_id: int | None = None warehouse_id: int delivery_date: datetime | None = None shipper_employee_id: int | None = None consignee_name: str | None = None consignee_phone: str | None = None delivery_address: str | None = None waybill_no: str | None = None order_photo_url: str | None = None freight_amount: float | None = None remark: str | None = None items: list[DeliveryItemCreate] class DeliveryRead(BaseModel): delivery_id: int delivery_no: str sales_order_id: int | None = None customer_id: int | None = None order_no: str | None = None customer_name: str | None = None warehouse_id: int warehouse_name: str delivery_date: datetime shipper_employee_id: int | None = None shipper_name: str | None = None consignee_name: str | None = None consignee_phone: str | None = None delivery_address: str | None = None logistics_waybill_no: str | None = None logistics_freight_amount: float | None = None logistics_photo_url: str | None = None product_names: str | None = None status: str line_count: int total_delivery_qty: float total_line_amount: float remark: str | None = None archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class DeliveryItemRead(BaseModel): delivery_item_id: int delivery_id: int delivery_no: str line_no: int sales_order_item_id: int | None = None order_no: str | None = None product_item_id: int product_code: str product_name: str lot_id: int | None = None lot_no: str | None = None source_material_lot_id: int | None = None source_material_lot_no: str | None = None source_material_code: str | None = None source_material_name: str | None = None source_material_sub_batch_no: str | None = None source_material_summary: str | None = None delivery_qty: float delivery_weight_kg: float unit_price: float line_amount: float status: str remark: str | None = None class CustomerSuppliedInboundCreate(BaseModel): material_item_id: int warehouse_id: int location_id: int | None = None provider_name: str | None = None inbound_weight_kg: float lot_no: str | None = None remark: str | None = None class InventorySettlementRead(BaseModel): settlement_type: str source_doc_type: str source_doc_id: int doc_no: str counterparty_name: str biz_date: datetime amount: float settled_amount: float = 0 open_amount: float status: str remark: str | None = None class EquipmentCreate(BaseModel): equipment_name: str category_name: str | None = None work_center_id: int manufacturer: str | None = None model_no: str | None = None install_date: date | None = None rated_power_kw: float = 0 occupied_area_sqm: float = 0 maintenance_cycle_hours: float = 0 next_maintenance_at: datetime | None = None status: str = "IDLE" remark: str | None = None class EquipmentRead(BaseModel): equipment_id: int equipment_code: str equipment_name: str category_name: str | None = None work_center_id: int work_center_name: str manufacturer: str | None = None model_no: str | None = None install_date: date | None = None rated_power_kw: float occupied_area_sqm: float maintenance_cycle_hours: float last_maintenance_at: datetime | None = None next_maintenance_at: datetime | None = None status: str remark: str | None = None class MaintenanceOrderCreate(BaseModel): equipment_id: int maintenance_type: str start_time: datetime | None = None end_time: datetime | None = None downtime_minutes: float = 0 owner_employee_id: int | None = None result_summary: str | None = None cost_amount: float = 0 status: str = "CREATED" class MaintenanceOrderRead(BaseModel): maintenance_order_id: int order_no: str equipment_id: int equipment_code: str equipment_name: str maintenance_type: str start_time: datetime | None = None end_time: datetime | None = None downtime_minutes: float owner_employee_id: int | None = None owner_name: str | None = None result_summary: str | None = None cost_amount: float status: str class QualityInspectionRead(BaseModel): receipt_item_id: int receipt_id: int receipt_no: str po_no: str supplier_name: str target_warehouse_type: str = "RAW" material_item_id: int material_code: str material_name: str warehouse_name: str location_name: str | None = None lot_no: str received_qty: float received_weight_kg: float accepted_qty: float accepted_weight_kg: float status: str quality_status: str inspector_name: str | None = None receipt_date: datetime remark: str | None = None archive_status: str = "未生成" archive_version: int | None = None archive_created_at: datetime | None = None archive_error_message: str | None = None class QualityInspectionAction(BaseModel): result: str remark: str | None = None class AccountingPeriodCreate(BaseModel): year_no: int month_no: int start_date: date end_date: date close_status: str = "OPEN" class AccountingPeriodRead(BaseModel): period_id: int period_code: str year_no: int month_no: int start_date: date end_date: date close_status: str closed_at: datetime | None = None class OverheadEntryCreate(BaseModel): period_id: int overhead_type: str dept_id: int | None = None amount: float allocation_basis: str = "WORK_HOURS" description: str | None = None class OverheadEntryRead(BaseModel): overhead_entry_id: int period_id: int period_code: str year_no: int month_no: int overhead_type: str dept_id: int | None = None dept_name: str | None = None amount: float allocation_basis: str description: str | None = None created_at: datetime class ProductMaterialCostExpenseRead(BaseModel): key: str label: str amount: float class ProductMaterialCostRowRead(BaseModel): work_order_id: int work_order_no: str row_no: int biz_date: date | None = None product_code: str customer_name: str | None = None product_name: str supply_mode: str delivery_note_no: str | None = None order_qty: float actual_production_qty: float shipped_qty: float inventory_qty: float inventory_value: float untaxed_unit_price: float untaxed_total_amount: float purchase_material_weight_kg: float used_material_weight_kg: float material_inventory_weight_kg: float material_inventory_producible_qty: float material_inventory_value: float material_grade: str | None = None material_unit_price_kg: float gross_weight_g: float gross_material_cost_per_piece: float gross_material_total_cost: float net_weight_g: float scrap_weight_g: float scrap_unit_price_kg: float scrap_revenue_per_piece: float scrap_total_revenue: float material_cost_per_piece: float material_total_cost: float loss_rate: float loss_total_cost: float labor_cost_per_piece: float labor_total_cost: float outsource_cost_per_piece: float outsource_total_cost: float product_cost_per_piece: float product_cost_with_adjustment_per_piece: float product_total_cost: float rework_qty: float rework_cost_per_piece: float rework_total_cost: float scrap_qty: float scrap_residual_value_per_piece: float scrap_total_cost: float batch_adjustment_avg_cost: float class ProductMaterialCostSummaryRead(BaseModel): row_count: int sales_total: float gross_material_total: float scrap_revenue_total: float material_cost_total: float material_loss_total: float labor_total: float outsource_total: float rework_total: float scrap_total: float overhead_total: float direct_cost_total: float profit_amount: float cost_ratio: float class ProductMaterialCostBoardRead(BaseModel): title: str date_from: date date_to: date generated_at: datetime rows: list[ProductMaterialCostRowRead] expense_rows: list[ProductMaterialCostExpenseRead] summary: ProductMaterialCostSummaryRead class CostAllocationRead(BaseModel): cost_allocation_id: int period_id: int period_code: str work_order_id: int work_order_no: str product_item_id: int product_code: str product_name: str allocation_basis_value: float overhead_amount: float material_amount: float labor_amount: float machine_amount: float abnormal_loss_amount: float total_cost_amount: float unit_cost: float class CostAllocationGenerateResult(BaseModel): period_id: int period_code: str generated_count: int rows: list[CostAllocationRead] class StatementSnapshotCreate(BaseModel): period_id: int statement_type: str report_date: date class StatementSnapshotRead(BaseModel): statement_snapshot_id: int snapshot_no: str period_id: int period_code: str statement_type: str report_date: date summary_json: dict created_at: datetime class ReturnItemCreate(BaseModel): sales_order_item_id: int | None = None delivery_item_id: int | None = None product_item_id: int source_lot_id: int | None = None return_qty: float return_weight_kg: float = 0 responsibility_type: str | None = None remark: str | None = None class ReturnOrderCreate(BaseModel): customer_id: int sales_order_id: int | None = None delivery_id: int | None = None return_date: datetime | None = None return_reason: str | None = None handler_employee_id: int | None = None warehouse_id: int | None = None remark: str | None = None items: list[ReturnItemCreate] class ReturnScrapInboundItemCreate(BaseModel): delivery_item_id: int product_item_id: int source_lot_id: int | None = None scrap_qty: float = 0 scrap_weight_kg: float unit_cost: float | None = None remark: str | None = None class ReturnScrapInboundCreate(BaseModel): customer_id: int delivery_id: int warehouse_id: int inbound_date: datetime | None = None return_reason: str | None = None handler_employee_id: int | None = None remark: str | None = None items: list[ReturnScrapInboundItemCreate] class ReturnOrderRead(BaseModel): return_order_id: int return_no: str customer_id: int customer_name: str sales_order_id: int | None = None order_no: str | None = None delivery_id: int | None = None delivery_no: str | None = None return_date: datetime return_reason: str | None = None handler_employee_id: int | None = None handler_name: str | None = None product_names: str | None = None status: str line_count: int total_return_qty: float remark: str | None = None archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None class ReturnItemRead(BaseModel): return_item_id: int return_order_id: int return_no: str line_no: int sales_order_item_id: int | None = None delivery_item_id: int | None = None delivery_no: str | None = None product_item_id: int product_code: str product_name: str source_lot_id: int | None = None source_lot_no: str | None = None source_material_lot_id: int | None = None source_material_lot_no: str | None = None source_material_code: str | None = None source_material_name: str | None = None source_material_sub_batch_no: str | None = None source_material_summary: str | None = None return_qty: float return_weight_kg: float disposition_status: str responsibility_type: str | None = None remark: str | None = None class ReturnDispositionCreate(BaseModel): return_item_id: int disposition_type: str scrap_weight_kg: float = 0 extra_cost_amount: float = 0 scrap_sale_amount: float = 0 remark: str | None = None class ReturnDispositionRead(BaseModel): return_disposition_id: int disposition_no: str return_item_id: int return_no: str product_item_id: int | None = None product_code: str product_name: str return_qty: float = 0 return_weight_kg: float = 0 disposition_type: str rework_work_order_id: int | None = None rework_work_order_no: str | None = None rework_report_good_qty: float = 0 rework_report_scrap_qty: float = 0 rework_finished_inbound_qty: float = 0 rework_scrap_inbound_qty: float = 0 remaining_rework_finished_inbound_qty: float = 0 remaining_rework_scrap_inbound_qty: float = 0 rework_close_ready: bool = False rework_close_status: str | None = None scrap_weight_kg: float extra_cost_amount: float scrap_sale_amount: float closed_at: datetime | None = None status: str remark: str | None = None archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None # --- Warehouse unified inbound/outbound schemas --- BIZ_TYPE_INBOUND = { "OPENING", # 期初入库 "CUSTOMER_SUPPLIED", # 客料入库 "PRODUCTION_SURPLUS", # 生产余料入库 "OUTSOURCING_SURPLUS",# 委外余料入库 "PRODUCTION_SCRAP_IN",# 生产废料入库 "REWORK_SCRAP_IN", # 返工废料入库(废料库) "OUTSOURCING_SCRAP_IN",# 委外废料入库 "RETURN_IN", # 退货入库(退货库) "RETURN_SCRAP_IN", # 退货废料入库(废料库) "WIP_IN", # 产中入库(半成品) "OUTSOURCING_IN", # 委外入库(半成品/成品) "PRODUCTION_FINISHED",# 生产入库(成品) } BIZ_TYPE_OUTBOUND = { "PRODUCTION_OUT", # 生产出库(原材料/辅料) "OUTSOURCING_OUT", # 委外出库(原材料/半成品) "SCRAP_OUT", # 报废出库(原材料) "SCRAP_SALE_OUT", # 废料销售出库(废料库) "RETURN_OUT", # 返工出库(退货库) "WIP_OUT", # 产中出库(半成品) "SALES_OUT", # 销售出库(成品) } class WarehouseInboundCreate(BaseModel): biz_type: str item_id: int warehouse_id: int location_id: int | None = None source_lot_id: int | None = None inbound_weight_kg: float = 0 inbound_qty: float | None = 0 unit_cost: float | None = None lot_no: str | None = None source_doc_type: str | None = None source_doc_id: int | None = None source_line_id: int | None = None source_lot_ids: list[int] = Field(default_factory=list) source_material_sub_batch_no: str | None = None source_material_summary: str | None = None provider_name: str | None = None waybill_no: str | None = None order_photo_url: str | None = None freight_amount: float | None = None remark: str | None = None settle_work_order: bool = False settle_remark: str | None = None class OpeningInventoryImportResult(BaseModel): imported: int warehouse_type: str message: str class WarehouseOutboundSourceLotCreate(BaseModel): source_lot_id: int = Field(gt=0) outbound_weight_kg: float = Field(gt=0) class WarehouseOutboundCreate(BaseModel): biz_type: str item_id: int warehouse_id: int location_id: int | None = None outbound_weight_kg: float = 0 outbound_qty: float | None = 0 target_doc_type: str | None = None target_doc_id: int | None = None target_doc_line_id: int | None = None target_material_sub_batch_no: str | None = None sale_unit_price: float | None = None buyer_name: str | None = None sale_remark: str | None = None waybill_no: str | None = None order_photo_url: str | None = None freight_amount: float | None = None outsourcing_party_name: str | None = None remark: str | None = None source_lots: list[WarehouseOutboundSourceLotCreate] = Field(default_factory=list) class WarehouseTransferCreate(BaseModel): item_id: int source_warehouse_id: int target_warehouse_id: int transfer_weight_kg: float lot_id: int | None = None remark: str | None = None # --- Stocktake schemas --- class StocktakeStartCreate(BaseModel): warehouse_ids: list[int] = Field(min_length=1, max_length=1) remark: str | None = None class StocktakeWarehouseRead(BaseModel): id: int warehouse_id: int warehouse_name: str warehouse_type: str status: str class StocktakeLineRead(BaseModel): id: int stocktake_id: int line_no: int warehouse_id: int warehouse_name: str warehouse_type: str location_id: int | None = None location_name: str | None = None item_id: int item_code: str item_name: str item_type: str lot_id: int | None = None lot_no: str | None = None source_material_sub_batch_no: str | None = None snapshot_qty: float snapshot_weight_kg: float snapshot_unit_cost: float counted_qty: float | None = None counted_weight_kg: float | None = None diff_qty: float diff_weight_kg: float diff_amount: float diff_type: str row_status: str error_message: str | None = None remark: str | None = None class StocktakeRead(BaseModel): id: int stocktake_no: str status: str started_by_user_id: int | None = None imported_by_user_id: int | None = None confirmed_by_user_id: int | None = None canceled_by_user_id: int | None = None started_at: datetime exported_at: datetime | None = None imported_at: datetime | None = None confirmed_at: datetime | None = None canceled_at: datetime | None = None remark: str | None = None warehouses: list[StocktakeWarehouseRead] = Field(default_factory=list) summary: dict[str, float | int] = Field(default_factory=dict) class StocktakeImportPreviewRead(BaseModel): stocktake: StocktakeRead lines: list[StocktakeLineRead] summary: dict[str, float | int] class StocktakeConfirmCreate(BaseModel): confirm_text: str | None = None remark: str | None = None # --- Special warehouse adjustment schemas --- class SpecialWarehouseAdjustmentLineCreate(BaseModel): item_id: int lot_id: int | None = None location_id: int | None = None adjust_qty: Decimal | None = None adjust_weight_kg: Decimal | None = None unit_cost: Decimal | None = None line_reason: str | None = None class SpecialWarehouseAdjustmentCreate(BaseModel): warehouse_id: int direction: str reason: str | None = None lines: list[SpecialWarehouseAdjustmentLineCreate] = Field(min_length=1) class SpecialWarehouseAdjustmentLineRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int adjustment_id: int line_no: int item_id: int warehouse_id: int location_id: int | None = None lot_id: int inventory_txn_id: int | None = None before_qty: float change_qty: float after_qty: float before_weight_kg: float change_weight_kg: float after_weight_kg: float unit_cost: float line_reason: str | None = None class SpecialWarehouseAdjustmentRead(BaseModel): model_config = ConfigDict(from_attributes=True) id: int adjustment_no: str warehouse_id: int warehouse_type: str direction: str reason: str status: str operator_user_id: int | None = None adjusted_at: datetime lines: list[SpecialWarehouseAdjustmentLineRead] = Field(default_factory=list) archive_status: str | None = None archive_business_id: int | None = None archive_document_type: str | None = None archive_error_message: str | None = None