Files
suixinkan_ios_uikit/suixinkan_ios/Features/Orders/ViewModels/OrdersViewModel.swift
2026-06-26 14:33:31 +08:00

214 lines
7.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OrdersViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/22.
//
import Foundation
@MainActor
/// ViewModel
final class OrdersViewModel {
var onChange: (() -> Void)?
var selectedEntry: OrdersEntry = .storeOrders { didSet { onChange?() } }
var selectedStatus = 0 { didSet { onChange?() } }
var searchPhone = "" { didSet { onChange?() } }
var filterStartDate: Date? { didSet { onChange?() } }
var filterEndDate: Date? { didSet { onChange?() } }
private(set) var loading = false { didSet { onChange?() } }
private(set) var loadingMore = false { didSet { onChange?() } }
private(set) var isVerifying = false { didSet { onChange?() } }
private(set) var currentVerifyingOrderNumber: String? { didSet { onChange?() } }
private(set) var storeOrders: [OrderEntity] = [] { didSet { onChange?() } }
private(set) var storeTotal = 0 { didSet { onChange?() } }
private(set) var storePage = 1 { didSet { onChange?() } }
private(set) var storeHasMore = false { didSet { onChange?() } }
private(set) var writeOffOrders: [WriteOffOrderItem] = [] { didSet { onChange?() } }
private(set) var writeOffTotal = 0 { didSet { onChange?() } }
private(set) var writeOffPage = 1 { didSet { onChange?() } }
private(set) var writeOffHasMore = false { didSet { onChange?() } }
///
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
}()
}