// // OrdersViewModel.swift // suixinkan // // Created by Codex on 2026/6/22. // import Foundation import Combine @MainActor /// 订单页面 ViewModel,管理订单列表、核销列表、筛选条件和手动核销流程。 final class OrdersViewModel: ObservableObject { @Published var selectedEntry: OrdersEntry = .storeOrders @Published var selectedStatus = 0 @Published var searchPhone = "" @Published var filterStartDate: Date? @Published var filterEndDate: Date? @Published private(set) var loading = false @Published private(set) var loadingMore = false @Published private(set) var isVerifying = false @Published private(set) var currentVerifyingOrderNumber: String? @Published private(set) var storeOrders: [OrderEntity] = [] @Published private(set) var storeTotal = 0 @Published private(set) var storePage = 1 @Published private(set) var storeHasMore = false @Published private(set) var writeOffOrders: [WriteOffOrderItem] = [] @Published private(set) var writeOffTotal = 0 @Published private(set) var writeOffPage = 1 @Published private(set) var writeOffHasMore = false /// 返回去除首尾空白后的手机号搜索值。 var normalizedPhone: String? { let text = searchPhone.trimmingCharacters(in: .whitespacesAndNewlines) return text.isEmpty ? nil : text } /// 根据当前子入口刷新对应列表。 func reload(api: OrderServing, scenicId: Int?, storeId: Int? = nil, roleId: Int?, showLoading: Bool = true) async throws { switch selectedEntry { case .storeOrders: try await reloadStoreOrders(api: api, scenicId: scenicId, roleId: roleId, showLoading: showLoading) case .verificationOrders: try await reloadWriteOffOrders(api: api, scenicId: scenicId, storeId: storeId, showLoading: showLoading) } } /// 刷新订单管理列表,缺少景区时清空旧数据。 func reloadStoreOrders(api: OrderServing, scenicId: Int?, roleId: Int?, showLoading: Bool = true) async throws { guard let scenicId else { storeOrders = [] storeTotal = 0 storePage = 1 storeHasMore = false return } if showLoading { loading = true } defer { if showLoading { loading = false } } let result = try await api.orderList( scenicId: scenicId, page: 1, pageSize: pageSize, orderStatus: orderStatusQuery, userPhone: normalizedPhone, startTime: startTimeQuery, endTime: endTimeQuery, isRefined: refinedQuery, isScenicAdmin: roleId == 53 ) storeOrders = result.list storeTotal = result.total storePage = 1 storeHasMore = storeOrders.count < result.total && !result.list.isEmpty } /// 加载订单管理下一页。 func loadMoreStoreOrders(api: OrderServing, scenicId: Int?, roleId: Int?) async throws { guard !loadingMore, storeHasMore, let scenicId else { return } loadingMore = true defer { loadingMore = false } let nextPage = storePage + 1 let result = try await api.orderList( scenicId: scenicId, page: nextPage, pageSize: pageSize, orderStatus: orderStatusQuery, userPhone: normalizedPhone, startTime: startTimeQuery, endTime: endTimeQuery, isRefined: refinedQuery, isScenicAdmin: roleId == 53 ) storeOrders.append(contentsOf: result.list) storeTotal = result.total storePage = nextPage storeHasMore = storeOrders.count < result.total && !result.list.isEmpty } /// 刷新核销订单列表,缺少景区时清空旧数据。 func reloadWriteOffOrders(api: OrderServing, scenicId: Int?, storeId: Int? = nil, showLoading: Bool = true) async throws { guard let scenicId else { writeOffOrders = [] writeOffTotal = 0 writeOffPage = 1 writeOffHasMore = false return } if showLoading { loading = true } defer { if showLoading { loading = false } } let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize) writeOffOrders = result.list writeOffTotal = result.total writeOffPage = 1 writeOffHasMore = writeOffOrders.count < result.total && !result.list.isEmpty } /// 加载核销订单下一页。 func loadMoreWriteOffOrders(api: OrderServing, scenicId: Int?, storeId: Int? = nil) async throws { guard !loadingMore, writeOffHasMore, let scenicId else { return } loadingMore = true defer { loadingMore = false } let nextPage = writeOffPage + 1 let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: nextPage, pageSize: pageSize) writeOffOrders.append(contentsOf: result.list) writeOffTotal = result.total writeOffPage = nextPage writeOffHasMore = writeOffOrders.count < result.total && !result.list.isEmpty } /// 手动核销订单,并在成功后刷新核销列表。 func verify(api: OrderServing, scenicId: Int, storeId: Int? = nil, orderNumber: String) async throws { guard !isVerifying else { return } isVerifying = true currentVerifyingOrderNumber = orderNumber defer { isVerifying = false currentVerifyingOrderNumber = nil } try await api.writeOff(orderNumber: orderNumber) let refreshed = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize) writeOffOrders = refreshed.list writeOffTotal = refreshed.total writeOffPage = 1 writeOffHasMore = writeOffOrders.count < refreshed.total && !refreshed.list.isEmpty } /// 解析扫码结果并返回当前核销列表中的匹配订单。 func matchedWriteOffOrder(for rawCode: String) -> (orderNumber: String, matched: WriteOffOrderItem?)? { guard let orderNumber = OrderNumberParser.parse(rawCode) else { return nil } let matched = writeOffOrders.first { $0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame } return (matched?.orderNumber ?? orderNumber, matched) } /// 根据订单号查找当前核销列表中的订单。 func writeOffOrder(matching orderNumber: String) -> WriteOffOrderItem? { writeOffOrders.first { $0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame } } /// 用于测试重复提交场景的内部状态设置。 func markVerifyingForTests(orderNumber: String) { isVerifying = true currentVerifyingOrderNumber = orderNumber } private let pageSize = 20 private var orderStatusQuery: Int? { selectedStatus > 0 ? selectedStatus : nil } private var startTimeQuery: String? { filterStartDate.map { Self.dayFormatter.string(from: $0) } } private var endTimeQuery: String? { filterEndDate.map { Self.dayFormatter.string(from: $0) } } private var refinedQuery: Int? { switch selectedStatus { case -1: return 1 case -2: return 2 default: return nil } } private static let dayFormatter: DateFormatter = { let formatter = DateFormatter() formatter.calendar = Calendar(identifier: .gregorian) formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "yyyy-MM-dd" return formatter }() }