Files
suixinkan_uikit/suixinkan/Features/Orders/ViewModels/OrderListViewModel.swift

474 lines
16 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.

//
// OrderListViewModel.swift
// suixinkan
//
import Foundation
struct MultiVerifyParseData: Equatable {
let orderNumber: String
let userId: Int
let time: Int64
let checkSum: String
}
struct OrderSourcePerson: Equatable {
let key: String
let name: String
let phone: String
}
struct OrderSourceLead: Equatable {
let key: String
let displayText: String
let userMobile: String
let createdAt: String
}
struct OrderSourceSelection: Equatable {
let person: OrderSourcePerson
let lead: OrderSourceLead
}
/// / ViewModel Android `OrderListViewModel`
final class OrderListViewModel {
private(set) var orderList: [OrderEntity] = []
private(set) var searchText = ""
private(set) var filterLabel = "全部"
private(set) var tabIndex = 0
private(set) var rTabIndex = 0
private(set) var startTime: Date?
private(set) var endTime: Date?
private(set) var isRefreshing = false
private(set) var canLoadMore = false
private(set) var isLoadingMore = false
private(set) var multiVerifyPendingData: MultiVerifyParseData?
private(set) var multiVerifyScenicSpotList: [MultiTravelScenicSpotEntity] = []
private(set) var selectedMultiVerifySpot: MultiTravelScenicSpotEntity?
private(set) var signInResponse: OrderSignInResponse?
private(set) var writeOffResponse: WriteOffOrderResponse?
private(set) var orderSourcePeople: [OrderSourcePerson] = []
private(set) var orderSourceLeads: [String: [OrderSourceLead]] = [:]
private(set) var orderSourceSelections: [String: OrderSourceSelection] = [:]
private(set) var loadingOrderSourceLeadsFor: String?
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
private var currentPage = 1
private var totalCount = 0
private var loadingOrderSourcePeople = false
private let apiDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "zh_CN")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
var isScenicAdminReadOnly: Bool {
AppStore.shared.currentAppRole == .scenicAdmin
}
var listAPIPath: String {
isScenicAdminReadOnly
? "/api/app/scenic-admin/order/list"
: "/api/yf-handset-app/photog/order/listv2"
}
func refreshOrderList(api: OrderAPI) async {
currentPage = 1
isRefreshing = true
isLoadingMore = false
orderList = []
notifyStateChange()
await loadOrderList(api: api, append: false)
}
func loadMore(api: OrderAPI) async {
guard !isLoadingMore, !isRefreshing else { return }
guard orderList.count < totalCount else { return }
isLoadingMore = true
currentPage += 1
notifyStateChange()
await loadOrderList(api: api, append: true)
}
func shouldLoadMore(at index: Int) -> Bool {
guard canLoadMore, !isRefreshing, !isLoadingMore else { return false }
return index >= max(0, orderList.count - 3)
}
func onSearchTextChange(_ text: String) {
searchText = text
notifyStateChange()
}
func onFilterChange(orderStatus: Int, isRefined: Int) {
tabIndex = orderStatus
rTabIndex = isRefined
filterLabel = PhotographerOrderFilter.from(tabIndex: orderStatus, rTabIndex: isRefined).label
notifyStateChange()
}
func onDateRangeSelected(start: Date?, end: Date?) {
startTime = start
endTime = end
notifyStateChange()
}
func cancelOrder(orderNumber: String, api: OrderAPI) async {
guard !abortIfScenicAdminReadOnlyMutation() else { return }
do {
try await api.cancelOrder(CancelRequest(orderNumber: orderNumber))
onShowMessage?("取消成功")
await refreshOrderList(api: api)
} catch {
onShowMessage?(error.localizedDescription)
}
}
func refundOrder(_ request: OrderRefundRequest, api: OrderAPI) async {
guard !abortIfScenicAdminReadOnlyMutation() else { return }
do {
try await api.refundOrder(request)
onShowMessage?("退款成功")
await refreshOrderList(api: api)
} catch {
onShowMessage?(error.localizedDescription)
}
}
func sendGift(orderNumber: String, photoNum: Int, videoNum: Int, api: OrderAPI) async {
guard !abortIfScenicAdminReadOnlyMutation() else { return }
do {
try await api.giftRetouch(SendGiftRequest(orderNumber: orderNumber, photoNum: photoNum, videoNum: videoNum))
onShowMessage?("赠送成功")
await refreshOrderList(api: api)
} catch {
onShowMessage?(error.localizedDescription)
}
}
func updateRefined(orderNumber: String, isRefined: Int, api: OrderAPI) async {
guard !abortIfScenicAdminReadOnlyMutation() else { return }
do {
try await api.updateRefined(orderNumber: orderNumber, isRefined: isRefined)
await refreshOrderList(api: api)
} catch {
onShowMessage?(error.localizedDescription)
}
}
func processScanResult(_ scanResult: String, api: OrderAPI) async {
if scanResult.isEmpty {
onShowMessage?("请扫描正确的二维码")
return
}
guard !abortIfScenicAdminReadOnlyMutation() else { return }
if let parsed = QRCodeScanResult.parse(scanResult) {
switch parsed.method {
case "multi_verify":
guard !parsed.orderNumber.isEmpty, parsed.userId > 0, parsed.timeLong > 0, !parsed.checkSum.isEmpty else {
onShowMessage?("请扫描正确的多点位核销码")
return
}
multiVerifyPendingData = MultiVerifyParseData(
orderNumber: parsed.orderNumber,
userId: parsed.userId,
time: parsed.timeLong,
checkSum: parsed.checkSum
)
notifyStateChange()
return
case SaleUserQrParser.methodSaleUserBind:
if let saleUserId = SaleUserQrParser.parseSaleUserId(scanResult), saleUserId > 0 {
onShowMessage?("获客员绑定功能待接入")
} else {
onShowMessage?("请扫描正确的获客员二维码")
}
return
case "order_verify":
await performOrderVerify(jsonBody: parsed.rawJSON, api: api)
return
case "check_in", "":
await performCheckIn(scanResult: scanResult, api: api)
return
default:
break
}
}
await performCheckIn(scanResult: scanResult, api: api)
}
func fetchScenicSpotsAndShowSheet(lat: Double, lng: Double, api: OrderAPI) async {
guard let data = multiVerifyPendingData else { return }
do {
let response = try await api.multiTravelScenicSpotList(
orderNumber: data.orderNumber,
lat: lat,
lng: lng
)
multiVerifyScenicSpotList = response.entityList
selectedMultiVerifySpot = multiVerifyScenicSpotList.first
notifyStateChange()
if multiVerifyScenicSpotList.isEmpty {
onShowMessage?("暂无可核销的打卡点")
clearMultiVerifyPending()
}
} catch {
onShowMessage?(error.localizedDescription)
clearMultiVerifyPending()
}
}
func onMultiVerifySpotSelected(_ spot: MultiTravelScenicSpotEntity) {
selectedMultiVerifySpot = spot
notifyStateChange()
}
func onMultiVerifyConfirm(api: OrderAPI) async {
guard !abortIfScenicAdminReadOnlyMutation() else { return }
guard let data = multiVerifyPendingData, let spot = selectedMultiVerifySpot else {
onShowMessage?("请选择一个打卡点")
return
}
do {
let response = try await api.multiTravelOrderVerify(
MultiTravelOrderVerifyRequest(
orderNumber: data.orderNumber,
userId: data.userId,
time: data.time,
checkSum: data.checkSum,
scenicSpotId: spot.scenicSpotId
)
)
signInResponse = response
clearMultiVerifySheet()
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
clearMultiVerifyPending()
}
}
func clearMultiVerifySheet() {
multiVerifyScenicSpotList = []
selectedMultiVerifySpot = nil
multiVerifyPendingData = nil
notifyStateChange()
}
func clearMultiVerifyPending() {
multiVerifyPendingData = nil
notifyStateChange()
}
func clearSignInResponse() {
signInResponse = nil
notifyStateChange()
}
func clearWriteOffResponse() {
writeOffResponse = nil
notifyStateChange()
}
func shouldShowOrderSource(for order: OrderEntity) -> Bool {
guard !isScenicAdminReadOnly else { return false }
return order.orderType == OrderTypeCode.photographerFollow
|| order.orderType == OrderTypeCode.photographerFollowOfflineScan
}
func orderSourceSelection(for order: OrderEntity) -> OrderSourceSelection? {
if let selection = orderSourceSelections[order.orderNumber] { return selection }
guard let referral = order.referralOrder else { return nil }
let person = OrderSourcePerson(
key: String(referral.saleUserId),
name: referral.displaySalerName,
phone: referral.salerPhone
)
let lead = OrderSourceLead(
key: referral.orderNo,
displayText: referral.displayText.isEmpty ? referral.orderNo : referral.displayText,
userMobile: referral.userMobile,
createdAt: referral.createdAt
)
return OrderSourceSelection(person: person, lead: lead)
}
func onOrderSourceSheetOpen(api: OrderAPI) async {
orderSourceLeads = [:]
guard !loadingOrderSourcePeople else { return }
loadingOrderSourcePeople = true
defer { loadingOrderSourcePeople = false }
let storeId = AppStore.shared.currentStoreId
let storeIdParam = storeId > 0 ? String(storeId) : nil
do {
let response = try await api.cooperativeSalers(storeId: storeIdParam)
orderSourcePeople = response.list.map {
OrderSourcePerson(key: String($0.saleUserId), name: $0.displayName, phone: $0.displayPhone)
}
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
func loadOrderSourceLeads(person: OrderSourcePerson, api: OrderAPI) async {
guard loadingOrderSourceLeadsFor == nil else { return }
loadingOrderSourceLeadsFor = person.key
notifyStateChange()
defer {
loadingOrderSourceLeadsFor = nil
notifyStateChange()
}
do {
let response = try await api.referralOrders(saleUserId: person.key)
let leads = response.list.map {
OrderSourceLead(
key: $0.displayReferralOrderNo,
displayText: $0.displayReferralOrderNo,
userMobile: $0.userMobile,
createdAt: $0.createdAt
)
}
orderSourceLeads[person.key] = leads
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
func bindOrderSource(orderNumber: String, selection: OrderSourceSelection, api: OrderAPI) async {
guard !abortIfScenicAdminReadOnlyMutation() else { return }
do {
try await api.bindReferralOrder(orderNumber: orderNumber, referralOrderNo: selection.lead.key)
orderSourceSelections[orderNumber] = selection
onShowMessage?("绑定成功")
await refreshOrderList(api: api)
} catch {
onShowMessage?(error.localizedDescription)
}
}
private func loadOrderList(api: OrderAPI, append: Bool) async {
let scenicId = AppStore.shared.currentScenicId
guard scenicId > 0 else {
finishLoading()
onShowMessage?("当前景区信息缺失")
return
}
let start = startTime.map { apiDateFormatter.string(from: $0) }
let end = endTime.map { apiDateFormatter.string(from: $0) }
let phone = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
do {
let response: OrderListResponse<OrderEntity>
if isScenicAdminReadOnly {
response = try await api.scenicAdminOrderList(
scenicId: scenicId,
page: currentPage,
orderStatus: tabIndex,
startTime: start,
endTime: end,
userPhone: phone.isEmpty ? nil : phone,
isRefined: rTabIndex
)
} else {
response = try await api.photographerOrderList(
scenicId: scenicId,
page: currentPage,
orderStatus: tabIndex,
startTime: start,
endTime: end,
userPhone: phone.isEmpty ? nil : phone,
isRefined: rTabIndex
)
}
totalCount = response.total
if append {
orderList.append(contentsOf: response.list)
} else {
orderList = response.list
}
syncOrderSourceSelections()
canLoadMore = orderList.count < totalCount
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
finishLoading()
}
private func performCheckIn(scanResult: String, api: OrderAPI) async {
guard let jsonBody = appendScenicId(to: scanResult) else {
onShowMessage?("请扫描正确的二维码")
return
}
do {
signInResponse = try await api.orderCheckIn(jsonBody: jsonBody)
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
private func performOrderVerify(jsonBody: String, api: OrderAPI) async {
do {
writeOffResponse = try await api.orderVerify(jsonBody: jsonBody)
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
private func appendScenicId(to scanResult: String) -> String? {
guard let data = scanResult.data(using: .utf8),
var json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return nil
}
json["scenic_id"] = String(AppStore.shared.currentScenicId)
guard let updated = try? JSONSerialization.data(withJSONObject: json),
let string = String(data: updated, encoding: .utf8) else {
return nil
}
return string
}
private func syncOrderSourceSelections() {
var updated = orderSourceSelections
for order in orderList {
if let selection = orderSourceSelection(for: order) {
updated[order.orderNumber] = selection
}
}
orderSourceSelections = updated
}
@discardableResult
private func abortIfScenicAdminReadOnlyMutation() -> Bool {
guard isScenicAdminReadOnly else { return false }
onShowMessage?("景区管理员仅可查看")
return true
}
private func finishLoading() {
isRefreshing = false
isLoadingMore = false
notifyStateChange()
}
private func notifyStateChange() {
onStateChange?()
}
}