新增门店订单与核销管理列表,对齐 Android 订单能力并完善账号级缓存。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 14:46:10 +08:00
parent c4de3d17d0
commit 08492df6ce
51 changed files with 3668 additions and 102 deletions

View File

@ -36,8 +36,21 @@ final class OrdersViewModel: ObservableObject {
@Published var orderSourceSelections: [String: OrderSourceSelection] = [:]
@Published var loadingOrderSourceLeadsFor: String?
@Published var refundingOrder: OrderEntity?
@Published var sendGiftContext: SendGiftContext?
private var loadingOrderSourcePeople = false
/// /
struct SendGiftContext: Equatable, Identifiable {
var id: String { orderNumber }
let orderNumber: String
let nowPhotoNum: Int
let nowVideoNum: Int
var sendPhotoNum: Int
var sendVideoNum: Int
}
static let orderTypePhotographerFollow = 11
static let orderTypePhotographerFollowOfflineScan = 14
@ -125,7 +138,17 @@ final class OrdersViewModel: ObservableObject {
if showLoading { loading = true }
defer { if showLoading { loading = false } }
let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize)
let result = try await api.writeOffList(
scenicId: scenicId,
storeId: storeId,
page: 1,
pageSize: pageSize,
orderStatus: nil,
orderVerifyStatus: nil,
startTime: nil,
endTime: nil,
userPhone: nil
)
writeOffOrders = result.list
writeOffTotal = result.total
writeOffPage = 1
@ -139,7 +162,17 @@ final class OrdersViewModel: ObservableObject {
defer { loadingMore = false }
let nextPage = writeOffPage + 1
let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: nextPage, pageSize: pageSize)
let result = try await api.writeOffList(
scenicId: scenicId,
storeId: storeId,
page: nextPage,
pageSize: pageSize,
orderStatus: nil,
orderVerifyStatus: nil,
startTime: nil,
endTime: nil,
userPhone: nil
)
writeOffOrders.append(contentsOf: result.list)
writeOffTotal = result.total
writeOffPage = nextPage
@ -158,13 +191,82 @@ final class OrdersViewModel: ObservableObject {
}
try await api.writeOff(orderNumber: orderNumber)
let refreshed = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize)
let refreshed = try await api.writeOffList(
scenicId: scenicId,
storeId: storeId,
page: 1,
pageSize: pageSize,
orderStatus: nil,
orderVerifyStatus: nil,
startTime: nil,
endTime: nil,
userPhone: nil
)
writeOffOrders = refreshed.list
writeOffTotal = refreshed.total
writeOffPage = 1
writeOffHasMore = writeOffOrders.count < refreshed.total && !refreshed.list.isEmpty
}
/// 退
func shouldShowRefundButton(order: OrderEntity, appRole: AppRoleCode?) -> Bool {
guard !Self.isScenicAdminReadOnly(appRole: appRole) else { return false }
return (order.orderStatus == 18 || order.orderStatus == 30) && order.orderType != 19
}
///
func shouldShowVerifyButton(order: OrderEntity, appRole: AppRoleCode?) -> Bool {
guard !Self.isScenicAdminReadOnly(appRole: appRole) else { return false }
if order.orderType == 19 { return true }
guard let photoTravel = order.photoTravel else { return false }
return photoTravel.needCheckIn && order.orderStatus != 30
}
///
func updateRefinedChoice(api: OrderServing, orderNumber: String, isRefined: Int) async throws {
try await api.updateRefinedChoice(orderNumber: orderNumber, isRefined: isRefined)
if let index = storeOrders.firstIndex(where: { $0.orderNumber == orderNumber }) {
storeOrders[index].isRefined = isRefined
}
}
///
func cancelOrder(api: OrderServing, scenicId: Int?, appRole: AppRoleCode?, orderNumber: String) async throws {
try await api.cancelOrder(orderNumber: orderNumber)
try await reloadStoreOrders(api: api, scenicId: scenicId, appRole: appRole, showLoading: false)
}
/// /
func prepareSendGift(for order: OrderEntity) {
let photoTravel = order.photoTravel
sendGiftContext = SendGiftContext(
orderNumber: order.orderNumber,
nowPhotoNum: photoTravel?.orderPhotoNum ?? 0,
nowVideoNum: photoTravel?.orderVideoNum ?? 0,
sendPhotoNum: photoTravel?.retouchGiftPhotoNum ?? 0,
sendVideoNum: photoTravel?.retouchGiftVideoNum ?? 0
)
}
/// /
func submitSendGift(
api: OrderServing,
scenicId: Int?,
appRole: AppRoleCode?,
photoNum: Int,
videoNum: Int
) async throws {
guard let context = sendGiftContext else { return }
try await api.sendGift(orderNumber: context.orderNumber, photoNum: photoNum, videoNum: videoNum)
sendGiftContext = nil
try await reloadStoreOrders(api: api, scenicId: scenicId, appRole: appRole, showLoading: false)
}
///
var currentFilterTitle: String {
OrderFilters.statusFilters.first(where: { $0.id == selectedStatus })?.title ?? "全部"
}
///
func shouldShowOrderSource(order: OrderEntity, appRole: AppRoleCode?) -> Bool {
guard !Self.isScenicAdminReadOnly(appRole: appRole) else { return false }

View File

@ -0,0 +1,112 @@
//
// StoreAdminOrderListViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import Foundation
import Combine
@MainActor
/// Tab ViewModel Android DepositOrderListViewModel
final class StoreAdminOrderListViewModel: ObservableObject {
@Published var searchPhone = ""
@Published var selectedStatus = 0
@Published var filterStartDate: Date?
@Published var filterEndDate: Date?
@Published private(set) var orders: [StoreOrderListItem] = []
@Published private(set) var loading = false
@Published private(set) var loadingMore = false
@Published private(set) var total = 0
@Published private(set) var page = 1
@Published private(set) var hasMore = false
private let pageSize = 20
///
var normalizedPhone: String? {
let text = searchPhone.trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? nil : text
}
///
var currentFilterTitle: String {
OrderFilters.storeAdminStatusFilters.first(where: { $0.id == selectedStatus })?.title ?? "全部"
}
///
func reload(api: OrderServing, storeId: Int?, showLoading: Bool = true) async throws {
guard let storeId, storeId > 0 else {
orders = []
total = 0
page = 1
hasMore = false
return
}
if showLoading { loading = true }
defer { if showLoading { loading = false } }
let result = try await api.storeOrderList(
storeId: storeId,
page: 1,
pageSize: pageSize,
userPhone: normalizedPhone,
orderStatus: orderStatusQuery,
orderType: nil,
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate)
)
orders = result.list
total = result.total
page = 1
hasMore = orders.count < result.total && !result.list.isEmpty
}
///
func loadMore(api: OrderServing, storeId: Int?) async throws {
guard !loadingMore, hasMore, let storeId, storeId > 0 else { return }
loadingMore = true
defer { loadingMore = false }
let nextPage = page + 1
let result = try await api.storeOrderList(
storeId: storeId,
page: nextPage,
pageSize: pageSize,
userPhone: normalizedPhone,
orderStatus: orderStatusQuery,
orderType: nil,
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate)
)
orders.append(contentsOf: result.list)
total = result.total
page = nextPage
hasMore = orders.count < result.total && !result.list.isEmpty
}
/// 退
func refund(
api: OrderServing,
storeId: Int?,
orderNumber: String,
mode: OrderRefundMode,
amount: String,
reason: String
) async throws {
guard let storeId, storeId > 0 else { return }
try await api.storeOrderRefund(
storeId: storeId,
orderNumber: orderNumber,
refundType: mode,
refundAmount: amount,
refundReason: reason
)
}
private var orderStatusQuery: Int? {
selectedStatus > 0 ? selectedStatus : nil
}
}

View File

@ -0,0 +1,141 @@
//
// WriteOffListViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/29.
//
import Foundation
import Combine
@MainActor
/// ViewModel Android WriteOffListViewModel
final class WriteOffListViewModel: ObservableObject {
@Published var searchPhone = ""
@Published var selectedStatus = 0
@Published var filterStartDate: Date?
@Published var filterEndDate: Date?
@Published private(set) var orders: [WriteOffOrderItem] = []
@Published private(set) var loading = false
@Published private(set) var loadingMore = false
@Published private(set) var isVerifying = false
@Published private(set) var total = 0
@Published private(set) var page = 1
@Published private(set) var hasMore = false
private let pageSize = 20
///
var normalizedPhone: String? {
let text = searchPhone.trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? nil : text
}
///
func reload(api: OrderServing, scenicId: Int?, storeId: Int? = nil, showLoading: Bool = true) async throws {
guard let scenicId else {
orders = []
total = 0
page = 1
hasMore = 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,
orderStatus: orderStatusQuery,
orderVerifyStatus: verifyStatusQuery,
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate),
userPhone: normalizedPhone
)
orders = result.list
total = result.total
page = 1
hasMore = orders.count < result.total && !result.list.isEmpty
}
///
func loadMore(api: OrderServing, scenicId: Int?, storeId: Int? = nil) async throws {
guard !loadingMore, hasMore, let scenicId else { return }
loadingMore = true
defer { loadingMore = false }
let nextPage = page + 1
let result = try await api.writeOffList(
scenicId: scenicId,
storeId: storeId,
page: nextPage,
pageSize: pageSize,
orderStatus: orderStatusQuery,
orderVerifyStatus: verifyStatusQuery,
startTime: OrderDateRangeFormatter.queryValue(from: filterStartDate),
endTime: OrderDateRangeFormatter.queryValue(from: filterEndDate),
userPhone: normalizedPhone
)
orders.append(contentsOf: result.list)
total = result.total
page = nextPage
hasMore = orders.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
defer { isVerifying = false }
try await api.writeOff(orderNumber: orderNumber)
try await reload(api: api, scenicId: scenicId, storeId: storeId, showLoading: false)
}
///
func matchedWriteOffOrder(for rawCode: String) -> (orderNumber: String, matched: WriteOffOrderItem?)? {
guard let orderNumber = OrderNumberParser.parse(rawCode) else { return nil }
let matched = orders.first {
$0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame
}
return (matched?.orderNumber ?? orderNumber, matched)
}
///
func writeOffOrder(matching orderNumber: String) -> WriteOffOrderItem? {
orders.first { $0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame }
}
///
func canVerify(_ item: WriteOffOrderItem) -> Bool {
let status = displayVerificationStatus(for: item)
return !status.contains("已核销") && !status.contains("已退款") && !status.contains("已完成")
}
///
func displayVerificationStatus(for item: WriteOffOrderItem) -> String {
let text = item.orderVerificationStatus.trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? "待核销" : text
}
private var orderStatusQuery: Int? {
switch selectedStatus {
case 18, 30:
return selectedStatus
default:
return nil
}
}
private var verifyStatusQuery: Int? {
switch selectedStatus {
case 1, 2:
return selectedStatus
default:
return nil
}
}
}