Wire deposit orders, historical shooting, and multi-travel uploads into Orders, and replace home placeholders for queue management, message center, scenic settlement, and withdrawal audit. Co-authored-by: Cursor <cursoragent@cursor.com>
369 lines
15 KiB
Swift
369 lines
15 KiB
Swift
//
|
||
// ScenicQueueSettingsModels.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/25.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 排队设置响应。
|
||
struct ScenicQueueSettingData: Decodable, Equatable {
|
||
let exists: Bool
|
||
let setting: ScenicQueueSettingItem?
|
||
let time: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case exists
|
||
case setting
|
||
case time
|
||
}
|
||
|
||
/// 创建排队设置响应。
|
||
init(exists: Bool = false, setting: ScenicQueueSettingItem? = nil, time: String? = nil) {
|
||
self.exists = exists
|
||
self.setting = setting
|
||
self.time = time
|
||
}
|
||
|
||
/// 宽松解码排队设置响应。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
exists = try container.decodeLossyBool(forKey: .exists) ?? false
|
||
setting = try container.decodeIfPresent(ScenicQueueSettingItem.self, forKey: .setting)
|
||
time = try container.decodeLossyString(forKey: .time)
|
||
}
|
||
}
|
||
|
||
/// 排队设置实体。
|
||
struct ScenicQueueSettingItem: Decodable, Equatable {
|
||
let id: Int64?
|
||
let scenicId: Int?
|
||
let scenicSpotId: Int?
|
||
let scenicSpotName: String
|
||
let photoEstimateMin: Int
|
||
let photoEstimateSec: Int
|
||
let firstNoticeThresholdPos: Int
|
||
let firstNoticeSmsEnabled: Int
|
||
let firstNoticeCallEnabled: Int
|
||
let secondNoticeThresholdPos: Int
|
||
let secondNoticeSmsEnabled: Int
|
||
let secondNoticeCallEnabled: Int
|
||
let countdownBroadcastIntervalSec: Int
|
||
let countdownReadableThresholdSec: Int
|
||
let queueDistanceMeter: Int
|
||
let queueTakeLimit: Int
|
||
let missCallRequeueOffset: Int
|
||
let showStartShootButton: Int?
|
||
let autoCallNextCount: Int?
|
||
let businessStartTime: String
|
||
let businessEndTime: String
|
||
let status: Int
|
||
let remark: String
|
||
let voiceBroadcasts: [ScenicQueueVoiceBroadcastItem]
|
||
let createdAt: String
|
||
let updatedAt: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case scenicId = "scenic_id"
|
||
case scenicSpotId = "scenic_spot_id"
|
||
case scenicSpotName = "scenic_spot_name"
|
||
case photoEstimateMin = "photo_estimate_min"
|
||
case photoEstimateSec = "photo_estimate_sec"
|
||
case firstNoticeThresholdPos = "first_notice_threshold_pos"
|
||
case firstNoticeSmsEnabled = "first_notice_sms_enabled"
|
||
case firstNoticeCallEnabled = "first_notice_call_enabled"
|
||
case secondNoticeThresholdPos = "second_notice_threshold_pos"
|
||
case secondNoticeSmsEnabled = "second_notice_sms_enabled"
|
||
case secondNoticeCallEnabled = "second_notice_call_enabled"
|
||
case countdownBroadcastIntervalSec = "countdown_broadcast_interval_sec"
|
||
case countdownReadableThresholdSec = "countdown_readable_threshold_sec"
|
||
case queueDistanceMeter = "queue_distance_meter"
|
||
case queueTakeLimit = "queue_take_limit"
|
||
case missCallRequeueOffset = "miss_call_requeue_offset"
|
||
case showStartShootButton = "show_start_shoot_button"
|
||
case autoCallNextCount = "auto_call_next_count"
|
||
case businessStartTime = "business_start_time"
|
||
case businessEndTime = "business_end_time"
|
||
case status
|
||
case remark
|
||
case voiceBroadcasts = "voice_broadcasts"
|
||
case createdAt = "created_at"
|
||
case updatedAt = "updated_at"
|
||
}
|
||
|
||
/// 宽松解码排队设置实体。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = (try container.decodeLossyInt(forKey: .id)).map(Int64.init)
|
||
scenicId = try container.decodeLossyInt(forKey: .scenicId)
|
||
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId)
|
||
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
|
||
photoEstimateMin = try container.decodeLossyInt(forKey: .photoEstimateMin) ?? 0
|
||
photoEstimateSec = try container.decodeLossyInt(forKey: .photoEstimateSec) ?? 0
|
||
firstNoticeThresholdPos = try container.decodeLossyInt(forKey: .firstNoticeThresholdPos) ?? 0
|
||
firstNoticeSmsEnabled = try container.decodeLossyInt(forKey: .firstNoticeSmsEnabled) ?? 0
|
||
firstNoticeCallEnabled = try container.decodeLossyInt(forKey: .firstNoticeCallEnabled) ?? 0
|
||
secondNoticeThresholdPos = try container.decodeLossyInt(forKey: .secondNoticeThresholdPos) ?? 0
|
||
secondNoticeSmsEnabled = try container.decodeLossyInt(forKey: .secondNoticeSmsEnabled) ?? 0
|
||
secondNoticeCallEnabled = try container.decodeLossyInt(forKey: .secondNoticeCallEnabled) ?? 0
|
||
countdownBroadcastIntervalSec = try container.decodeLossyInt(forKey: .countdownBroadcastIntervalSec) ?? 50
|
||
countdownReadableThresholdSec = try container.decodeLossyInt(forKey: .countdownReadableThresholdSec) ?? 15
|
||
queueDistanceMeter = try container.decodeLossyInt(forKey: .queueDistanceMeter) ?? 0
|
||
queueTakeLimit = try container.decodeLossyInt(forKey: .queueTakeLimit) ?? 0
|
||
missCallRequeueOffset = try container.decodeLossyInt(forKey: .missCallRequeueOffset) ?? 1
|
||
showStartShootButton = try container.decodeLossyInt(forKey: .showStartShootButton)
|
||
autoCallNextCount = try container.decodeLossyInt(forKey: .autoCallNextCount)
|
||
businessStartTime = try container.decodeLossyString(forKey: .businessStartTime)
|
||
businessEndTime = try container.decodeLossyString(forKey: .businessEndTime)
|
||
status = try container.decodeLossyInt(forKey: .status) ?? 1
|
||
remark = try container.decodeLossyString(forKey: .remark)
|
||
voiceBroadcasts = (try? container.decodeIfPresent([ScenicQueueVoiceBroadcastItem].self, forKey: .voiceBroadcasts)) ?? []
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
updatedAt = try container.decodeLossyString(forKey: .updatedAt)
|
||
}
|
||
}
|
||
|
||
/// 排队语音播报预设。
|
||
struct ScenicQueueVoiceBroadcastItem: Codable, Identifiable, Equatable {
|
||
var id: String { "\(sortOrder)-\(content)" }
|
||
let content: String
|
||
let sortOrder: Int
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case content
|
||
case sortOrder = "sort_order"
|
||
}
|
||
|
||
/// 创建语音播报预设。
|
||
init(content: String, sortOrder: Int) {
|
||
self.content = content
|
||
self.sortOrder = sortOrder
|
||
}
|
||
|
||
/// 宽松解码语音播报预设。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
content = try container.decodeLossyString(forKey: .content).trimmingCharacters(in: .whitespacesAndNewlines)
|
||
sortOrder = try container.decodeLossyInt(forKey: .sortOrder) ?? 0
|
||
}
|
||
}
|
||
|
||
/// 保存排队设置请求体。
|
||
struct ScenicQueueSaveSettingRequest: Encodable, Equatable {
|
||
let scenicId: Int
|
||
let scenicSpotId: Int
|
||
let photoEstimateMin: Int
|
||
let photoEstimateSec: Int
|
||
let firstNoticeThresholdPos: Int
|
||
let firstNoticeSmsEnabled: Int
|
||
let firstNoticeCallEnabled: Int
|
||
let secondNoticeThresholdPos: Int
|
||
let secondNoticeSmsEnabled: Int
|
||
let secondNoticeCallEnabled: Int
|
||
let countdownBroadcastIntervalSec: Int
|
||
let countdownReadableThresholdSec: Int
|
||
let queueDistanceMeter: Int?
|
||
let queueTakeLimit: Int?
|
||
let missCallRequeueOffset: Int?
|
||
let showStartShootButton: Int?
|
||
let autoCallNextCount: Int?
|
||
let businessStartTime: String
|
||
let businessEndTime: String
|
||
let voiceBroadcasts: [ScenicQueueVoiceBroadcastItem]
|
||
let status: Int
|
||
let remark: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case scenicId = "scenic_id"
|
||
case scenicSpotId = "scenic_spot_id"
|
||
case photoEstimateMin = "photo_estimate_min"
|
||
case photoEstimateSec = "photo_estimate_sec"
|
||
case firstNoticeThresholdPos = "first_notice_threshold_pos"
|
||
case firstNoticeSmsEnabled = "first_notice_sms_enabled"
|
||
case firstNoticeCallEnabled = "first_notice_call_enabled"
|
||
case secondNoticeThresholdPos = "second_notice_threshold_pos"
|
||
case secondNoticeSmsEnabled = "second_notice_sms_enabled"
|
||
case secondNoticeCallEnabled = "second_notice_call_enabled"
|
||
case countdownBroadcastIntervalSec = "countdown_broadcast_interval_sec"
|
||
case countdownReadableThresholdSec = "countdown_readable_threshold_sec"
|
||
case queueDistanceMeter = "queue_distance_meter"
|
||
case queueTakeLimit = "queue_take_limit"
|
||
case missCallRequeueOffset = "miss_call_requeue_offset"
|
||
case showStartShootButton = "show_start_shoot_button"
|
||
case autoCallNextCount = "auto_call_next_count"
|
||
case businessStartTime = "business_start_time"
|
||
case businessEndTime = "business_end_time"
|
||
case voiceBroadcasts = "voice_broadcasts"
|
||
case status
|
||
case remark
|
||
}
|
||
}
|
||
|
||
/// 排队设置变更日志响应。
|
||
struct ScenicQueueSettingChangeLogData: Decodable, Equatable {
|
||
let total: Int
|
||
let page: Int
|
||
let pageSize: Int
|
||
let list: [ScenicQueueSettingChangeLogItem]
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case total
|
||
case page
|
||
case pageSize = "page_size"
|
||
case list
|
||
}
|
||
|
||
/// 创建变更日志响应。
|
||
init(total: Int = 0, page: Int = 1, pageSize: Int = 20, list: [ScenicQueueSettingChangeLogItem] = []) {
|
||
self.total = total
|
||
self.page = page
|
||
self.pageSize = pageSize
|
||
self.list = list
|
||
}
|
||
|
||
/// 宽松解码变更日志响应。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
total = try container.decodeLossyInt(forKey: .total) ?? 0
|
||
page = try container.decodeLossyInt(forKey: .page) ?? 1
|
||
pageSize = try container.decodeLossyInt(forKey: .pageSize) ?? 20
|
||
list = (try? container.decodeIfPresent([ScenicQueueSettingChangeLogItem].self, forKey: .list)) ?? []
|
||
}
|
||
}
|
||
|
||
/// 排队设置变更日志项。
|
||
struct ScenicQueueSettingChangeLogItem: Decodable, Equatable, Identifiable {
|
||
let id: Int64
|
||
let scenicSpotName: String
|
||
let operatorName: String
|
||
let operatorPhoneTail: String
|
||
let summary: String
|
||
let displayText: String
|
||
let createdAt: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case scenicSpotName = "scenic_spot_name"
|
||
case operatorName = "operator_name"
|
||
case operatorPhoneTail = "operator_phone_tail"
|
||
case summary
|
||
case displayText = "display_text"
|
||
case createdAt = "created_at"
|
||
}
|
||
|
||
/// 宽松解码变更日志项。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
|
||
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
|
||
operatorName = try container.decodeLossyString(forKey: .operatorName)
|
||
operatorPhoneTail = try container.decodeLossyString(forKey: .operatorPhoneTail)
|
||
summary = try container.decodeLossyString(forKey: .summary)
|
||
displayText = try container.decodeLossyString(forKey: .displayText)
|
||
createdAt = try container.decodeLossyString(forKey: .createdAt)
|
||
}
|
||
}
|
||
|
||
/// 排队取号二维码响应。
|
||
struct ScenicQueueShootQueueQRCodeData: Decodable, Equatable {
|
||
let qrcodeUrl: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case qrcodeUrl = "qrcode_url"
|
||
}
|
||
|
||
/// 创建二维码响应。
|
||
init(qrcodeUrl: String = "") {
|
||
self.qrcodeUrl = qrcodeUrl
|
||
}
|
||
|
||
/// 宽松解码二维码响应。
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
qrcodeUrl = try container.decodeLossyString(forKey: .qrcodeUrl)
|
||
}
|
||
}
|
||
|
||
/// 本地排队设置快照,用于服务器无配置或离线时保留关键配置。
|
||
struct ScenicQueueSettingsSnapshot: Codable, Equatable {
|
||
var shootMinute: Int = 0
|
||
var shootSecond: Int = 30
|
||
var firstAheadCount: Int = 0
|
||
var firstSms: Bool = false
|
||
var firstPhone: Bool = false
|
||
var secondAheadCount: Int = 0
|
||
var secondSms: Bool = false
|
||
var secondPhone: Bool = false
|
||
var broadcastIntervalSec: Int = 50
|
||
var countdownThresholdSec: Int = 15
|
||
var queueDistanceMeter: Int = 0
|
||
var queueTakeLimit: Int = 0
|
||
var missCallRequeueOffset: Int = 1
|
||
var showStartShootingButton: Bool = true
|
||
var autoCallAheadCount: Int = 0
|
||
var quickCallButtonEnabled: Bool = false
|
||
var prepareCallButtonEnabled: Bool = false
|
||
var businessOpen: Bool = true
|
||
var businessStartTime: String = "10:00"
|
||
var businessEndTime: String = "20:00"
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case shootMinute = "shoot_minute"
|
||
case shootSecond = "shoot_second"
|
||
case firstAheadCount = "first_ahead_count"
|
||
case firstSms = "first_sms"
|
||
case firstPhone = "first_phone"
|
||
case secondAheadCount = "second_ahead_count"
|
||
case secondSms = "second_sms"
|
||
case secondPhone = "second_phone"
|
||
case broadcastIntervalSec = "broadcast_interval_sec"
|
||
case countdownThresholdSec = "countdown_threshold_sec"
|
||
case queueDistanceMeter = "queue_distance_meter"
|
||
case queueTakeLimit = "queue_take_limit"
|
||
case missCallRequeueOffset = "miss_call_requeue_offset"
|
||
case showStartShootingButton = "show_start_shooting_button"
|
||
case autoCallAheadCount = "auto_call_ahead_count"
|
||
case quickCallButtonEnabled = "quick_call_button_enabled"
|
||
case prepareCallButtonEnabled = "prepare_call_button_enabled"
|
||
case businessOpen = "business_open"
|
||
case businessStartTime = "business_start_time"
|
||
case businessEndTime = "business_end_time"
|
||
}
|
||
}
|
||
|
||
private extension KeyedDecodingContainer {
|
||
func decodeLossyString(forKey key: Key) throws -> String {
|
||
if let value = try? decodeIfPresent(String.self, forKey: key) { return 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 ""
|
||
}
|
||
|
||
func decodeLossyInt(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),
|
||
case let text = value.trimmingCharacters(in: .whitespacesAndNewlines),
|
||
!text.isEmpty {
|
||
return Int(text) ?? Int(Double(text) ?? 0)
|
||
}
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) { return value ? 1 : 0 }
|
||
return nil
|
||
}
|
||
|
||
func decodeLossyBool(forKey key: Key) throws -> Bool? {
|
||
if let value = try? decodeIfPresent(Bool.self, forKey: key) { return value }
|
||
if let value = try? decodeIfPresent(Int.self, forKey: key) { return value != 0 }
|
||
if let value = try? decodeIfPresent(String.self, forKey: key),
|
||
case let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(),
|
||
!text.isEmpty {
|
||
if ["1", "true", "yes"].contains(text) { return true }
|
||
if ["0", "false", "no"].contains(text) { return false }
|
||
}
|
||
return nil
|
||
}
|
||
}
|