添加景区选择与合作订单流程并对齐 Android

This commit is contained in:
2026-07-07 10:48:43 +08:00
parent ef1d3b4af5
commit c4057537d2
50 changed files with 5603 additions and 191 deletions

View File

@ -206,6 +206,93 @@ final class OrderAPI {
)
}
func referralLeads(page: String = "1", pageSize: String = "10") async throws -> OrderListResponse<ReferralLeadEntity> {
try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/sale-user/referral-leads",
queryItems: [
URLQueryItem(name: "page", value: page),
URLQueryItem(name: "page_size", value: pageSize),
]
)
)
}
func acquisitionOrders(keyword: String? = nil, page: String = "1", pageSize: String = "10") async throws -> OrderListResponse<AcquisitionOrderEntity> {
var items = [
URLQueryItem(name: "page", value: page),
URLQueryItem(name: "page_size", value: pageSize),
]
if let keyword, !keyword.isEmpty {
items.append(URLQueryItem(name: "keyword", value: keyword))
}
return try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/sale-user/acquisition-orders",
queryItems: items
)
)
}
func saleUserInviteInfo(saleUserId: String) async throws -> SaleUserInviteInfoResponse {
try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/sale-user/invite-info",
queryItems: [URLQueryItem(name: "sale_user_id", value: saleUserId)]
)
)
}
func saleUserAcceptBind(saleUserId: Int, commissionRate: Int) async throws {
let _: EmptyPayload = try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/sale-user/accept-bind",
queryItems: [
URLQueryItem(name: "sale_user_id", value: String(saleUserId)),
URLQueryItem(name: "commission_rate", value: String(commissionRate)),
]
)
)
}
func saleUserUpdateRemark(saleUserId: Int, photogRemarkName: String) async throws {
let _: EmptyPayload = try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/sale-user/update-remark",
body: SaleUserUpdateRemarkRequest(saleUserId: saleUserId, photogRemarkName: photogRemarkName)
)
)
}
func saleUserCommissionRateSendSmsCode(phone: String) async throws {
let _: EmptyPayload = try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/sale-user/commission-rate/send-sms-code",
body: SaleUserCommissionRateSmsRequest(phone: phone)
)
)
}
func saleUserUpdateCommissionRate(saleUserId: Int, commissionRate: Int, smsCode: String) async throws -> CooperativeSalerEntity {
try await client.send(
APIRequest(
method: .post,
path: "/api/yf-handset-app/photog/sale-user/commission-rate",
body: SaleUserCommissionRateUpdateRequest(
saleUserId: saleUserId,
commissionRate: commissionRate,
smsCode: smsCode
)
)
)
}
func shootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
try await client.send(
APIRequest(

View File

@ -789,10 +789,19 @@ struct CooperativeSalerEntity: Decodable, Equatable {
let photogRemarkName: String
let phone: String
let salerPhone: String
let commissionRate: Int
let commissionRateLabel: String
let bindTime: String
let boundAt: String
var displayId: Int { saleUserId }
var displayName: String { OrderDisplayName.saleUser(remark: photogRemarkName, primary: name, fallback: salerName) }
var displayPhone: String { salerPhone.isEmpty ? phone : salerPhone }
var displayBindTime: String { bindTime.isEmpty ? boundAt : bindTime }
var displayCommissionRate: String {
if !commissionRateLabel.isEmpty { return commissionRateLabel }
return "\(commissionRate)%"
}
enum CodingKeys: String, CodingKey {
case saleUserId = "sale_user_id"
@ -802,6 +811,67 @@ struct CooperativeSalerEntity: Decodable, Equatable {
case photogRemarkName = "photog_remark_name"
case phone
case salerPhone = "saler_phone"
case commissionRate = "commission_rate"
case commissionRateLabel = "commission_rate_label"
case bindTime = "bind_time"
case boundAt = "bound_at"
}
init(
saleUserId: Int = 0,
bindingId: Int = 0,
name: String = "",
salerName: String = "",
photogRemarkName: String = "",
phone: String = "",
salerPhone: String = "",
commissionRate: Int = 0,
commissionRateLabel: String = "",
bindTime: String = "",
boundAt: String = ""
) {
self.saleUserId = saleUserId
self.bindingId = bindingId
self.name = name
self.salerName = salerName
self.photogRemarkName = photogRemarkName
self.phone = phone
self.salerPhone = salerPhone
self.commissionRate = commissionRate
self.commissionRateLabel = commissionRateLabel
self.bindTime = bindTime
self.boundAt = boundAt
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
saleUserId = try container.decodeIfPresent(Int.self, forKey: .saleUserId) ?? 0
bindingId = try container.decodeIfPresent(Int.self, forKey: .bindingId) ?? 0
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? ""
photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName) ?? ""
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? ""
salerPhone = try container.decodeIfPresent(String.self, forKey: .salerPhone) ?? ""
commissionRate = try container.decodeIfPresent(Int.self, forKey: .commissionRate) ?? 0
commissionRateLabel = try container.decodeIfPresent(String.self, forKey: .commissionRateLabel) ?? ""
bindTime = try container.decodeIfPresent(String.self, forKey: .bindTime) ?? ""
boundAt = try container.decodeIfPresent(String.self, forKey: .boundAt) ?? ""
}
func withRemarkName(_ remark: String) -> CooperativeSalerEntity {
CooperativeSalerEntity(
saleUserId: saleUserId,
bindingId: bindingId,
name: name,
salerName: salerName,
photogRemarkName: remark,
phone: phone,
salerPhone: salerPhone,
commissionRate: commissionRate,
commissionRateLabel: commissionRateLabel,
bindTime: bindTime,
boundAt: boundAt
)
}
}
@ -819,6 +889,7 @@ struct ReferralOrderEntity: Decodable, Equatable {
let createdAt: String
let status: Int
let statusLabel: String
let images: [String]
var displayReferralOrderNo: String { referralOrderNo.isEmpty ? orderNo : referralOrderNo }
@ -831,6 +902,20 @@ struct ReferralOrderEntity: Decodable, Equatable {
case createdAt = "created_at"
case status
case statusLabel = "status_label"
case images
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
referralOrderNo = try container.decodeIfPresent(String.self, forKey: .referralOrderNo) ?? ""
orderNo = try container.decodeIfPresent(String.self, forKey: .orderNo) ?? ""
userMobile = try container.decodeIfPresent(String.self, forKey: .userMobile) ?? ""
remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? ""
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
status = try container.decodeIfPresent(Int.self, forKey: .status) ?? 0
statusLabel = try container.decodeIfPresent(String.self, forKey: .statusLabel) ?? ""
images = (try? container.decode([String].self, forKey: .images)) ?? []
}
}

View File

@ -15,6 +15,7 @@ final class MainTabScanHandler: NSObject, CLLocationManagerDelegate {
var onShowWriteOff: ((WriteOffOrderResponse) -> Void)?
var onMultiVerifyPending: ((MultiVerifyParseData) -> Void)?
var onShowMultiVerifySheet: (([MultiTravelScenicSpotEntity], MultiTravelScenicSpotEntity?) -> Void)?
var onNavigateBindAcquirer: ((Int) -> Void)?
private let api: OrderAPI
private let locationManager = CLLocationManager()
@ -64,7 +65,7 @@ final class MainTabScanHandler: NSObject, CLLocationManagerDelegate {
await fetchSpotsForMultiVerify(pending: pending)
case SaleUserQrParser.methodSaleUserBind:
if let id = SaleUserQrParser.parseSaleUserId(scanResult), id > 0 {
onShowMessage?("获客员绑定功能待接入")
onNavigateBindAcquirer?(id)
} else {
onShowMessage?("请扫描正确的获客员二维码")
}

View File

@ -20,9 +20,59 @@ struct OrderSourcePerson: Equatable {
struct OrderSourceLead: Equatable {
let key: String
let displayText: String
let userMobile: String
let createdAt: String
let userPhone: String
let remark: String
let images: [String]
}
enum OrderSourceMapping {
/// API Android `toOrderSourceLead()`
static func lead(from entity: ReferralOrderEntity) -> OrderSourceLead {
let orderNo = entity.displayReferralOrderNo
let remark: String
if !entity.remark.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
remark = entity.remark
} else if !entity.statusLabel.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
remark = entity.statusLabel
} else {
remark = "带客单 \(orderNo)"
}
return OrderSourceLead(
key: orderNo,
userPhone: entity.userMobile,
remark: remark,
images: entity.images
)
}
/// Android `toOrderSourceSelection()`
static func selection(from referral: BoundReferralOrderEntity) -> OrderSourceSelection? {
let referralNo = referral.orderNo.trimmingCharacters(in: .whitespacesAndNewlines)
guard !referralNo.isEmpty else { return nil }
let personName = referral.displaySalerName.isEmpty ? "" : referral.displaySalerName
let person = OrderSourcePerson(
key: referral.saleUserId > 0 ? String(referral.saleUserId) : "",
name: personName,
phone: referral.salerPhone
)
let remark: String
if !referral.remark.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
remark = referral.remark
} else if !referral.displayText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
remark = referral.displayText
} else {
remark = "带客单号 \(referralNo)"
}
let lead = OrderSourceLead(
key: referralNo,
userPhone: referral.userMobile,
remark: remark,
images: []
)
return OrderSourceSelection(person: person, lead: lead)
}
}
struct OrderSourceSelection: Equatable {
@ -54,13 +104,14 @@ final class OrderListViewModel {
private(set) var orderSourceLeads: [String: [OrderSourceLead]] = [:]
private(set) var orderSourceSelections: [String: OrderSourceSelection] = [:]
private(set) var loadingOrderSourceLeadsFor: String?
private(set) var isLoadingOrderSourcePeople = false
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var onNavigateBindAcquirer: ((Int) -> Void)?
private var currentPage = 1
private var totalCount = 0
private var loadingOrderSourcePeople = false
private let apiDateFormatter: DateFormatter = {
let formatter = DateFormatter()
@ -188,7 +239,7 @@ final class OrderListViewModel {
return
case SaleUserQrParser.methodSaleUserBind:
if let saleUserId = SaleUserQrParser.parseSaleUserId(scanResult), saleUserId > 0 {
onShowMessage?("获客员绑定功能待接入")
onNavigateBindAcquirer?(saleUserId)
} else {
onShowMessage?("请扫描正确的获客员二维码")
}
@ -287,27 +338,27 @@ final class OrderListViewModel {
}
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)
orderSourceSelection(orderNumber: order.orderNumber, referralOrder: order.referralOrder)
}
/// Sheet 使
func orderSourceSelection(orderNumber: String, referralOrder: BoundReferralOrderEntity? = nil) -> OrderSourceSelection? {
if let selection = orderSourceSelections[orderNumber] { return selection }
if let referral = referralOrder ?? orderList.first(where: { $0.orderNumber == orderNumber })?.referralOrder {
return OrderSourceMapping.selection(from: referral)
}
return nil
}
func onOrderSourceSheetOpen(api: OrderAPI) async {
orderSourceLeads = [:]
guard !loadingOrderSourcePeople else { return }
loadingOrderSourcePeople = true
defer { loadingOrderSourcePeople = false }
guard !isLoadingOrderSourcePeople else { return }
isLoadingOrderSourcePeople = true
notifyStateChange()
defer {
isLoadingOrderSourcePeople = false
notifyStateChange()
}
let storeId = AppStore.shared.currentStoreId
let storeIdParam = storeId > 0 ? String(storeId) : nil
do {
@ -331,15 +382,7 @@ final class OrderListViewModel {
}
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
orderSourceLeads[person.key] = response.list.map(OrderSourceMapping.lead(from:))
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)