新增门店订单与核销管理列表,对齐 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

@ -0,0 +1,224 @@
//
// StoreAdminOrderListViewModelTests.swift
// suixinkanTests
//
// Created by Codex on 2026/6/29.
//
import XCTest
@testable import suixinkan
@MainActor
/// Tab ViewModel 退
final class StoreAdminOrderListViewModelTests: XCTestCase {
///
func testReloadClearsDataWhenStoreIsMissing() async throws {
let api = StoreAdminMockOrderService()
api.responses = [try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")]
let viewModel = StoreAdminOrderListViewModel()
try await viewModel.reload(api: api, storeId: 66)
XCTAssertFalse(viewModel.orders.isEmpty)
try await viewModel.reload(api: api, storeId: nil)
XCTAssertTrue(viewModel.orders.isEmpty)
XCTAssertEqual(viewModel.total, 0)
XCTAssertEqual(viewModel.page, 1)
XCTAssertFalse(viewModel.hasMore)
XCTAssertEqual(api.listCalls.count, 1)
}
///
func testReloadUsesFilterQueries() async throws {
let api = StoreAdminMockOrderService()
api.responses = [
try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success"),
try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")
]
let viewModel = StoreAdminOrderListViewModel()
viewModel.searchPhone = " 13800000000 "
viewModel.selectedStatus = 30
viewModel.filterStartDate = Self.date("2026-05-01")
viewModel.filterEndDate = Self.date("2026-05-31")
try await viewModel.reload(api: api, storeId: 66)
XCTAssertEqual(api.listCalls.count, 1)
XCTAssertEqual(api.listCalls[0].storeId, 66)
XCTAssertEqual(api.listCalls[0].userPhone, "13800000000")
XCTAssertEqual(api.listCalls[0].orderStatus, 30)
XCTAssertEqual(api.listCalls[0].startTime, "2026-05-01")
XCTAssertEqual(api.listCalls[0].endTime, "2026-05-31")
viewModel.selectedStatus = 0
viewModel.searchPhone = ""
try await viewModel.reload(api: api, storeId: 66)
XCTAssertEqual(api.listCalls[1].orderStatus, nil)
XCTAssertNil(api.listCalls[1].userPhone)
}
///
func testLoadMoreStopsAfterLastPage() async throws {
let api = StoreAdminMockOrderService()
api.responses = [try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")]
let viewModel = StoreAdminOrderListViewModel()
try await viewModel.reload(api: api, storeId: 66)
try await viewModel.loadMore(api: api, storeId: 66)
XCTAssertEqual(viewModel.orders.count, 2)
XCTAssertEqual(api.listCalls.count, 1)
XCTAssertFalse(viewModel.hasMore)
}
/// 退
func testRefundUsesStoreParameters() async throws {
let api = StoreAdminMockOrderService()
let viewModel = StoreAdminOrderListViewModel()
try await viewModel.refund(
api: api,
storeId: 66,
orderNumber: "STORE001",
mode: .partial,
amount: "50.00",
reason: "部分退款"
)
XCTAssertEqual(api.refundCalls.count, 1)
XCTAssertEqual(api.refundCalls[0].storeId, 66)
XCTAssertEqual(api.refundCalls[0].orderNumber, "STORE001")
XCTAssertEqual(api.refundCalls[0].mode, .partial)
XCTAssertEqual(api.refundCalls[0].amount, "50.00")
XCTAssertEqual(api.refundCalls[0].reason, "部分退款")
}
private static func date(_ text: String) -> Date {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter.date(from: text)!
}
}
@MainActor
/// storeOrderList 退
private final class StoreAdminMockOrderService: OrderServing {
struct ListCall: Equatable {
let storeId: Int?
let page: Int
let userPhone: String?
let orderStatus: Int?
let startTime: String?
let endTime: String?
}
struct RefundCall: Equatable {
let storeId: Int
let orderNumber: String
let mode: OrderRefundMode
let amount: String
let reason: String
}
var responses: [ListPayload<StoreOrderListItem>] = []
private(set) var listCalls: [ListCall] = []
private(set) var refundCalls: [RefundCall] = []
func orderList(
scenicId: Int,
page: Int,
pageSize: Int,
orderStatus: Int?,
userPhone: String?,
startTime: String?,
endTime: String?,
isRefined: Int?,
isScenicAdmin: Bool
) async throws -> ListPayload<OrderEntity> {
ListPayload(total: 0, list: [])
}
func writeOffList(
scenicId: Int,
storeId: Int?,
page: Int,
pageSize: Int,
orderStatus: Int?,
orderVerifyStatus: Int?,
startTime: String?,
endTime: String?,
userPhone: String?
) async throws -> ListPayload<WriteOffOrderItem> {
ListPayload(total: 0, list: [])
}
func updateRefinedChoice(orderNumber: String, isRefined: Int) async throws {}
func cancelOrder(orderNumber: String) async throws {}
func sendGift(orderNumber: String, photoNum: Int, videoNum: Int) async throws {}
func storeOrderList(
storeId: Int?,
page: Int,
pageSize: Int,
userPhone: String?,
orderStatus: Int?,
orderType: Int?,
startTime: String?,
endTime: String?
) async throws -> ListPayload<StoreOrderListItem> {
listCalls.append(
ListCall(
storeId: storeId,
page: page,
userPhone: userPhone,
orderStatus: orderStatus,
startTime: startTime,
endTime: endTime
)
)
return responses.isEmpty ? ListPayload(total: 0, list: []) : responses.removeFirst()
}
func storeOrderRefund(
storeId: Int,
orderNumber: String,
refundType: OrderRefundMode,
refundAmount: String,
refundReason: String
) async throws {
refundCalls.append(
RefundCall(
storeId: storeId,
orderNumber: orderNumber,
mode: refundType,
amount: refundAmount,
reason: refundReason
)
)
}
func writeOff(orderNumber: String) async throws {}
func storeOrderDetail(storeId: Int, orderNumber: String) async throws -> StoreOrderDetailResponse {
try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")
}
func depositOrderList(scenicId: Int, page: Int, pageSize: Int) async throws -> ListPayload<DepositOrderListItem> {
ListPayload(total: 0, list: [])
}
func depositOrderWriteOff(orderNumber: String) async throws {}
func depositOrderRefund(orderNumber: String, refundReason: String) async throws {}
func storeOrderShootingDetail(
storeId: Int,
orderNumber: String,
scenicSpotId: Int,
photogUid: Int
) async throws -> StoreOrderShootingDetailResponse {
try TestFixture.payload(StoreOrderShootingDetailResponse.self, named: "store_order_shooting_detail_success")
}
func orderRefund(orderNumber: String, refundType: OrderRefundMode, refundAmount: String, refundReason: String) async throws {}
func multiTravelShootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
try TestFixture.payload(MultiTravelShootHistoryResponse.self, named: "multi_travel_shoot_history_success")
}
func multiTravelVerifiedScenicSpotList(orderNumber: String) async throws -> [MultiTravelVerifiedScenicSpotItem] { [] }
func multiTravelUploadMaterial(_ request: MultiTravelUploadMaterialRequest) async throws {}
}