// // ScheduleModels.swift // suixinkan // // Created by Codex on 2026/6/24. // import Foundation /// 排班日程实体,表示某一天的一条工作安排。 struct ScheduleItem: Decodable, Identifiable, Hashable { let id: Int let name: String let remark: String let startTime: String let endTime: String let scheduleDate: String let orderNumber: String? let userNickname: String? let userPhone: String? let orderStatus: Int? let orderStatusName: String? /// 字段映射,兼容后端下划线命名。 enum CodingKeys: String, CodingKey { case id case name case remark case startTime = "start_time" case endTime = "end_time" case scheduleDate = "schedule_date" case orderNumber = "order_number" case userNickname = "user_nickname" case userPhone = "user_phone" case orderStatus = "order_status" case orderStatusName = "order_status_name" } /// 宽松解码排班字段。 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeScheduleLossyInt(forKey: .id) ?? 0 name = try container.decodeScheduleLossyString(forKey: .name) remark = try container.decodeScheduleLossyString(forKey: .remark) startTime = try container.decodeScheduleLossyString(forKey: .startTime) endTime = try container.decodeScheduleLossyString(forKey: .endTime) scheduleDate = try container.decodeScheduleLossyString(forKey: .scheduleDate) orderNumber = container.decodeScheduleLossyOptionalString(forKey: .orderNumber) userNickname = container.decodeScheduleLossyOptionalString(forKey: .userNickname) userPhone = container.decodeScheduleLossyOptionalString(forKey: .userPhone) orderStatus = try container.decodeScheduleLossyInt(forKey: .orderStatus) orderStatusName = container.decodeScheduleLossyOptionalString(forKey: .orderStatusName) } } /// 新增排班请求实体。 struct AddScheduleRequest: Encodable, Equatable { let scenicId: String let name: String let remark: String let startTime: String let endTime: String let scheduleDate: String let orderNumber: String? /// 请求字段映射,兼容后端下划线命名。 enum CodingKeys: String, CodingKey { case scenicId = "scenic_id" case name case remark case startTime = "start_time" case endTime = "end_time" case scheduleDate = "schedule_date" case orderNumber = "order_number" } } /// 删除排班请求实体。 struct ScheduleDeleteRequest: Encodable, Equatable { let id: Int } /// 排班编辑表单实体,用于 ViewModel 校验和生成请求。 struct ScheduleDraft: Equatable { var name = "" var remark = "" var scheduleDate = "" var startTime = "" var endTime = "" var manualOrderNumber = "" var selectedOrder: AvailableOrderResponse? /// 计算最终订单号,优先使用选择的订单,手工输入作为兜底。 func finalOrderNumber() -> String? { let picked = selectedOrder?.orderNumber.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" let manual = manualOrderNumber.trimmingCharacters(in: .whitespacesAndNewlines) let value = picked.isEmpty ? manual : picked return value.isEmpty ? nil : value } } private extension KeyedDecodingContainer { /// 将 String、数字和 Bool 宽松解码为字符串。 func decodeScheduleLossyString(forKey key: Key) throws -> String { decodeScheduleLossyOptionalString(forKey: key) ?? "" } /// 将 String、数字和 Bool 宽松解码为可选字符串。 func decodeScheduleLossyOptionalString(forKey key: Key) -> String? { if let value = try? decodeIfPresent(String.self, forKey: key) { let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) return trimmed.isEmpty ? nil : value } if let value = try? decodeIfPresent(Int.self, forKey: key) { return String(value) } if let value = try? decodeIfPresent(Double.self, forKey: key) { return String(value) } if let value = try? decodeIfPresent(Bool.self, forKey: key) { return value ? "1" : "0" } return nil } /// 将 String、Double 和 Int 宽松解码为 Int。 func decodeScheduleLossyInt(forKey key: Key) throws -> Int? { if let value = try? decodeIfPresent(Int.self, forKey: key) { return value } if let value = try? decodeIfPresent(Double.self, forKey: key) { return Int(value) } if let value = try? decodeIfPresent(String.self, forKey: key) { let text = value.trimmingCharacters(in: .whitespacesAndNewlines) return Int(text) ?? Double(text).map(Int.init) } return nil } }