import Foundation /// 进入业务页面前的核验结果,不将非空未知记录解释为任何已知注销状态。 enum StoreAccountDeregistrationAccessDecision: Equatable { case notChecked case checking case allowed case cooling case unresolved case failed(String) case obsolete } /// 进入业务前核验当前身份;冷静期只能查询或由用户明确撤销,不自动重新登录。 final class StoreAccountDeregistrationAccessViewModel { /// 未核验与查询失败都不能创建普通业务根页面。 private(set) var decision: StoreAccountDeregistrationAccessDecision = .notChecked /// 防止同一页面重复发起查询。 private(set) var isChecking = false /// 最近一次通过身份核验的服务端状态,用于冷静期展示和撤销前比对。 private(set) var status: StoreAccountDeregistrationStatus? private var restrictionRevision = 0 private var requiresSubmissionReconciliation: Bool private let submissionStore: (any StoreAccountDeregistrationSubmissionTracking)? private var observedCooling = false /// 本机存在待核实提交意图时,即使暂时返回 null 也不直接恢复普通业务。 init(requiresSubmissionReconciliation: Bool = false, submissionStore: (any StoreAccountDeregistrationSubmissionTracking)? = nil) { self.submissionStore = submissionStore self.requiresSubmissionReconciliation = requiresSubmissionReconciliation || submissionStore?.hasUnresolvedSubmission == true } /// 新的限制信号优先于此前已发出的状态查询,防止旧正常响应覆盖新限制。 func recordRestriction() { restrictionRevision += 1 status = nil decision = .unresolved } /// 查询前后核对身份;旧身份响应和错误均不能决定新会话是否进入业务页。 func verify(api: any StoreAccountDeregistrationStatusServing, isCurrentIdentity: @escaping @MainActor () -> Bool) async { guard !isChecking else { return } isChecking = true let revision = restrictionRevision decision = .checking status = nil defer { isChecking = false } guard await isCurrentIdentity() else { decision = .obsolete; return } do { let result = try await api.status() guard await isCurrentIdentity() else { decision = .obsolete; return } guard restrictionRevision == revision else { return } apply(result) } catch { guard await isCurrentIdentity() else { decision = .obsolete; return } guard restrictionRevision == revision else { return } if case APIError.serverCode(150015, _) = error { decision = .unresolved } else if case StoreAccountDeregistrationError.sessionChanged = error { decision = .obsolete } else { decision = .failed(error.localizedDescription) } } } /// 用户确认后先重查同一申请再撤销,结果丢失时保留限制,禁止自动重发 POST。 func cancel(api: any StoreAccountDeregistrationServing, isCurrentIdentity: @escaping @MainActor () -> Bool) async { guard !isChecking, decision == .cooling, let recordID = status?.recordID else { return } isChecking = true let revision = restrictionRevision defer { isChecking = false } guard await isCurrentIdentity() else { decision = .obsolete; return } do { let current = try await api.status() guard await isCurrentIdentity() else { decision = .obsolete; return } guard restrictionRevision == revision else { return } guard current.isCooling, current.recordID == recordID else { applyCancellationCheck(current); return } try await api.cancel() guard await isCurrentIdentity() else { decision = .obsolete; return } guard restrictionRevision == revision else { return } let confirmed = try await api.status() guard await isCurrentIdentity() else { decision = .obsolete; return } guard restrictionRevision == revision else { return } applyCancellationCheck(confirmed) } catch { guard await isCurrentIdentity() else { decision = .obsolete; return } guard restrictionRevision == revision else { return } status = nil decision = .failed("撤销结果尚未确认,请重新查询状态。\n" + error.localizedDescription) } } private func applyCancellationCheck(_ result: StoreAccountDeregistrationStatus) { // 曾查到冷静期后,旧 null/草稿不能证明撤销完成;须重新核实完整状态。 guard result.isCooling || result.isCancelled else { status = result decision = .unresolved return } apply(result) } private func apply(_ result: StoreAccountDeregistrationStatus) { status = result if submissionStore?.hasUnresolvedSubmission == true { requiresSubmissionReconciliation = true } if result.isCancelled, submissionStore?.clearCancelledSubmission(matching: result) == true { requiresSubmissionReconciliation = false } if result.isCooling { observedCooling = true decision = .cooling } else { decision = result.permitsPreparation && !requiresSubmissionReconciliation && (!observedCooling || result.isCancelled) ? .allowed : .unresolved } } /// 150015 只影响发出请求时的同一门店身份凭证,不能按手机号传播限制。 static func restrictionApplies(requestToken: String?, session: AppSessionStore) -> Bool { guard session.accountType == .storeUser, let requestToken, !requestToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return false } return requestToken == session.token } }