feat: 添加获客员分成记录并重构列表

This commit is contained in:
2026-07-23 17:43:13 +08:00
parent 7ccbc5568d
commit 0e0415eec4
7 changed files with 945 additions and 95 deletions

View File

@ -0,0 +1,277 @@
//
// CommissionRateLogTests.swift
// suixinkanTests
//
import UIKit
import XCTest
@testable import suixinkan
///
@MainActor
final class CommissionRateLogTests: XCTestCase {
func testModelDecodesCompleteRecordAndBuildsBusinessDisplayText() throws {
let data = """
{
"list": [{
"id": 1,
"binding_id": 10,
"sale_user_id": 456,
"store_user_id": 732,
"before_rate": 10,
"before_rate_label": "10%",
"after_rate": 20,
"after_rate_label": "20%",
"operator_type": "store_user",
"operator_id": 732,
"operator_name": "",
"remark": " ",
"created_at": "2026-07-23 11:00:00"
}],
"total": 1,
"page": 1,
"page_size": 10
}
""".data(using: .utf8)!
let response = try JSONDecoder().decode(CommissionRateLogListResponse.self, from: data)
let item = try XCTUnwrap(response.list.first)
XCTAssertEqual(response.pageSize, 10)
XCTAssertEqual(item.bindingId, 10)
XCTAssertEqual(item.saleUserId, 456)
XCTAssertEqual(item.storeUserId, 732)
XCTAssertEqual(item.operatorType, "store_user")
XCTAssertEqual(item.operatorId, 732)
XCTAssertEqual(item.displayBeforeRate, "10%(旧)")
XCTAssertEqual(item.displayAfterRate, "20%(新)")
XCTAssertEqual(item.metadataLine, "2026-07-23 11:00")
XCTAssertEqual(item.displayRemark, "调整合作比例")
}
func testDisplayFallsBackToNumericRateUnknownOperatorAndRawTime() throws {
let data = """
{
"list": [{
"id": 2,
"binding_id": 0,
"sale_user_id": 456,
"store_user_id": 0,
"before_rate": 5,
"before_rate_label": "",
"after_rate": 15,
"after_rate_label": " ",
"operator_type": "",
"operator_id": 0,
"operator_name": "",
"remark": " ",
"created_at": ""
}],
"total": 1,
"page": 1,
"page_size": 10
}
""".data(using: .utf8)!
let item = try JSONDecoder().decode(CommissionRateLogListResponse.self, from: data).list[0]
XCTAssertEqual(item.displayBeforeRate, "5%")
XCTAssertEqual(item.displayAfterRate, "15%")
XCTAssertEqual(item.metadataLine, "服务端时间")
XCTAssertNil(item.displayRemark)
}
func testAPIBuildsPathAndClampsPaginationParameters() async throws {
let session = MockURLSession(responses: [envelope(list: [], total: 0, page: 1)])
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
_ = try await api.saleUserCommissionRateLogs(saleUserId: 456, page: 0, pageSize: 99)
let request = try XCTUnwrap(session.requests.first)
XCTAssertEqual(request.httpMethod, "GET")
XCTAssertEqual(request.url?.path, "/api/yf-handset-app/photog/sale-user/commission-rate/logs")
let query = URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)?.queryItems
XCTAssertEqual(query?.first { $0.name == "sale_user_id" }?.value, "456")
XCTAssertEqual(query?.first { $0.name == "page" }?.value, "1")
XCTAssertEqual(query?.first { $0.name == "page_size" }?.value, "50")
}
func testViewModelRetriesFailedPageAndDeduplicatesItems() async throws {
let page1 = envelope(
list: [
record(id: 1, before: 10, after: 20),
record(id: 1, before: 10, after: 20),
record(id: 2, before: 20, after: 30),
],
total: 3,
page: 1
)
let page2 = envelope(
list: [
record(id: 2, before: 20, after: 30),
record(id: 3, before: 30, after: 40),
],
total: 3,
page: 2
)
let response = HTTPURLResponse(
url: URL(string: "https://api-test.zhifly.cn/mock")!,
statusCode: 200,
httpVersion: nil,
headerFields: nil
)!
let session = MockURLSession(results: [
.success((page1, response)),
.failure(URLError(.timedOut)),
.success((page2, response)),
])
let api = OrderAPI(client: APIClient(environment: .testing, session: session))
let viewModel = CommissionRateLogViewModel(saleUserId: 456)
await viewModel.refresh(api: api)
await viewModel.loadMore(api: api)
XCTAssertEqual(viewModel.items.map(\.id), [1, 2])
XCTAssertTrue(viewModel.canLoadMore)
XCTAssertFalse(viewModel.isLoadingMore)
await viewModel.loadMore(api: api)
XCTAssertEqual(viewModel.items.map(\.id), [1, 2, 3])
XCTAssertFalse(viewModel.canLoadMore)
let requestedPages = session.requests.compactMap { request in
URLComponents(url: request.url!, resolvingAgainstBaseURL: false)?
.queryItems?
.first { $0.name == "page" }?
.value
}
XCTAssertEqual(requestedPages, ["1", "2", "2"])
}
func testTimelineCellOnlyMarksLatestRecord() {
let item = CommissionRateLogEntity(
id: 1,
bindingId: 10,
saleUserId: 456,
storeUserId: 732,
beforeRate: 10,
beforeRateLabel: "10%",
afterRate: 20,
afterRateLabel: "20%",
operatorType: "store_user",
operatorId: 732,
operatorName: "李师傅",
remark: "",
createdAt: "2026-07-23 11:00:00"
)
let cell = CommissionRateLogTimelineCell(style: .default, reuseIdentifier: nil)
cell.configure(with: item, isLatest: true)
XCTAssertTrue(cell.accessibilityLabel?.contains("最新记录") == true)
XCTAssertFalse(cell.accessibilityLabel?.contains("李师傅") == true)
cell.configure(with: item, isLatest: false)
XCTAssertFalse(cell.accessibilityLabel?.contains("最新记录") == true)
}
func testPageTitleUsesAcquirerNameAndFallsBackWhenEmpty() {
let namedController = CommissionRateLogViewController(
saleUserId: 456,
acquirerName: " 获客员小王 "
)
namedController.setupNavigationBar()
XCTAssertEqual(namedController.title, "给获客员小王的分成记录")
let fallbackController = CommissionRateLogViewController(
saleUserId: 456,
acquirerName: " "
)
fallbackController.setupNavigationBar()
XCTAssertEqual(fallbackController.title, "修改记录")
}
func testAcquirerCardUsesUnifiedActionsAndBusinessHierarchy() throws {
let cell = CooperationAcquirerCell(style: .default, reuseIdentifier: nil)
cell.configure(
with: CooperativeSalerEntity(
saleUserId: 456,
name: "获客员小王",
salerPhone: "13800138000",
commissionRate: 20,
commissionRateLabel: "20%",
bindTime: "2026-07-23 11:00:00"
)
)
let fittingSize = cell.contentView.systemLayoutSizeFitting(
CGSize(width: 390, height: UIView.layoutFittingCompressedSize.height),
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel
)
cell.frame = CGRect(x: 0, y: 0, width: 390, height: fittingSize.height)
cell.layoutIfNeeded()
let remarkButton = try XCTUnwrap(
subview(in: cell, identifier: "cooperation_acquirer_edit_remark") as? UIButton
)
let logButton = try XCTUnwrap(
subview(in: cell, identifier: "cooperation_acquirer_commission_logs") as? UIButton
)
let commissionButton = try XCTUnwrap(
subview(in: cell, identifier: "cooperation_acquirer_edit_commission") as? UIButton
)
let bindTimeLabel = try XCTUnwrap(
subview(in: cell, identifier: "cooperation_acquirer_bind_time") as? UILabel
)
XCTAssertEqual(remarkButton.bounds.height, AppSpacing.minTouchTarget, accuracy: 0.5)
XCTAssertEqual(logButton.bounds.height, remarkButton.bounds.height, accuracy: 0.5)
XCTAssertEqual(commissionButton.bounds.height, remarkButton.bounds.height, accuracy: 0.5)
XCTAssertEqual(logButton.bounds.width, remarkButton.bounds.width, accuracy: 0.5)
XCTAssertEqual(commissionButton.bounds.width, remarkButton.bounds.width, accuracy: 0.5)
XCTAssertEqual(bindTimeLabel.text, "绑定于 2026-07-23 11:00")
}
private func envelope(list: [[String: Any]], total: Int, page: Int) -> Data {
let object: [String: Any] = [
"code": 100000,
"msg": "success",
"data": [
"list": list,
"total": total,
"page": page,
"page_size": 10,
],
]
return try! JSONSerialization.data(withJSONObject: object)
}
private func record(id: Int, before: Int, after: Int) -> [String: Any] {
[
"id": id,
"binding_id": 10,
"sale_user_id": 456,
"store_user_id": 732,
"before_rate": before,
"before_rate_label": "\(before)%",
"after_rate": after,
"after_rate_label": "\(after)%",
"operator_type": "store_user",
"operator_id": 732,
"operator_name": "李师傅",
"remark": "",
"created_at": "2026-07-23 11:00:00",
]
}
private func subview(in root: UIView, identifier: String) -> UIView? {
if root.accessibilityIdentifier == identifier {
return root
}
for child in root.subviews {
if let match = subview(in: child, identifier: identifier) {
return match
}
}
return nil
}
}