Upgrade scenic picker with search, location sorting, permission apply/status, and blocking home dialog; add cooperation order module and order source picker improvements with unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
249 lines
9.4 KiB
Swift
249 lines
9.4 KiB
Swift
//
|
||
// CooperationOrderModels.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 合作订单功能常量与校验,对齐 Android `CooperationOrderFeature`。
|
||
enum CooperationOrderFeature {
|
||
static let uri = "cooperation_order"
|
||
static let remarkNameMaxLength = 10
|
||
|
||
static func remarkNameValidationMessage(_ remark: String) -> String? {
|
||
let trimmed = remark.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
if trimmed.count > remarkNameMaxLength {
|
||
return "备注名不能超过\(remarkNameMaxLength)个字符"
|
||
}
|
||
return nil
|
||
}
|
||
|
||
static func hasPermission(in permissions: [HomePermissionItem]) -> Bool {
|
||
permissions.contains { $0.uri == uri }
|
||
}
|
||
}
|
||
|
||
/// GET /photog/sale-user/referral-leads 获客线索。
|
||
struct ReferralLeadEntity: Decodable, Equatable {
|
||
let id: Int
|
||
let leadId: Int
|
||
let displayName: String
|
||
let photogRemarkName: String
|
||
let name: String
|
||
let salerName: String
|
||
let userPhone: String
|
||
let userMobileMasked: String
|
||
let phone: String
|
||
let remark: String
|
||
let createdAt: String
|
||
let imageUrls: [String]
|
||
let images: [String]
|
||
|
||
var displayId: String { String(leadId > 0 ? leadId : id) }
|
||
var displayPhone: String {
|
||
if !userMobileMasked.isEmpty { return userMobileMasked }
|
||
if !userPhone.isEmpty { return userPhone }
|
||
return phone
|
||
}
|
||
|
||
var displayImages: [String] { imageUrls.isEmpty ? images : imageUrls }
|
||
|
||
var displaySalerName: String {
|
||
if !displayName.isEmpty { return displayName }
|
||
return OrderDisplayName.saleUser(remark: photogRemarkName, primary: name, fallback: salerName)
|
||
}
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case leadId = "lead_id"
|
||
case displayName = "display_name"
|
||
case photogRemarkName = "photog_remark_name"
|
||
case name
|
||
case salerName = "saler_name"
|
||
case userPhone = "user_phone"
|
||
case userMobileMasked = "user_mobile_masked"
|
||
case phone
|
||
case remark
|
||
case createdAt = "created_at"
|
||
case imageUrls = "image_urls"
|
||
case images
|
||
}
|
||
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
|
||
leadId = try container.decodeIfPresent(Int.self, forKey: .leadId) ?? 0
|
||
displayName = try container.decodeIfPresent(String.self, forKey: .displayName) ?? ""
|
||
photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName) ?? ""
|
||
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
|
||
salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? ""
|
||
userPhone = try container.decodeIfPresent(String.self, forKey: .userPhone) ?? ""
|
||
userMobileMasked = try container.decodeIfPresent(String.self, forKey: .userMobileMasked) ?? ""
|
||
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? ""
|
||
remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? ""
|
||
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
|
||
imageUrls = try container.decodeIfPresent([String].self, forKey: .imageUrls) ?? []
|
||
images = try container.decodeIfPresent([String].self, forKey: .images) ?? []
|
||
}
|
||
}
|
||
|
||
/// GET /photog/sale-user/acquisition-orders 获客订单。
|
||
struct AcquisitionOrderEntity: Decodable, Equatable {
|
||
let id: Int
|
||
let orderNumber: String
|
||
let projectName: String
|
||
let orderType: Int
|
||
let orderTypeLabel: String
|
||
let orderAmount: String
|
||
let amount: String
|
||
let actualPayAmount: String
|
||
let createdAt: String
|
||
let customerPhone: String
|
||
let userMobileMasked: String
|
||
let phone: String
|
||
let saleUserName: String
|
||
let photogRemarkName: String
|
||
let partnerName: String
|
||
|
||
var displayAmount: String {
|
||
if !orderAmount.isEmpty { return orderAmount }
|
||
if !amount.isEmpty { return amount }
|
||
return actualPayAmount
|
||
}
|
||
|
||
var displayCustomerPhone: String {
|
||
if !userMobileMasked.isEmpty { return userMobileMasked }
|
||
if !customerPhone.isEmpty { return customerPhone }
|
||
return phone
|
||
}
|
||
|
||
var displayPartner: String {
|
||
let salerDisplay = OrderDisplayName.saleUser(remark: photogRemarkName, primary: saleUserName)
|
||
if !salerDisplay.isEmpty { return salerDisplay }
|
||
return partnerName
|
||
}
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case orderNumber = "order_number"
|
||
case projectName = "project_name"
|
||
case orderType = "order_type"
|
||
case orderTypeLabel = "order_type_label"
|
||
case orderAmount = "order_amount"
|
||
case amount
|
||
case actualPayAmount = "actual_pay_amount"
|
||
case createdAt = "created_at"
|
||
case customerPhone = "customer_phone"
|
||
case userMobileMasked = "user_mobile_masked"
|
||
case phone
|
||
case saleUserName = "sale_user_name"
|
||
case photogRemarkName = "photog_remark_name"
|
||
case partnerName = "partner_name"
|
||
}
|
||
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
|
||
orderNumber = try container.decodeIfPresent(String.self, forKey: .orderNumber) ?? ""
|
||
projectName = try container.decodeIfPresent(String.self, forKey: .projectName) ?? ""
|
||
orderType = try container.decodeIfPresent(Int.self, forKey: .orderType) ?? 0
|
||
orderTypeLabel = try container.decodeIfPresent(String.self, forKey: .orderTypeLabel) ?? ""
|
||
orderAmount = try container.decodeIfPresent(String.self, forKey: .orderAmount) ?? ""
|
||
amount = try container.decodeIfPresent(String.self, forKey: .amount) ?? ""
|
||
actualPayAmount = try container.decodeIfPresent(String.self, forKey: .actualPayAmount) ?? ""
|
||
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
|
||
customerPhone = try container.decodeIfPresent(String.self, forKey: .customerPhone) ?? ""
|
||
userMobileMasked = try container.decodeIfPresent(String.self, forKey: .userMobileMasked) ?? ""
|
||
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? ""
|
||
saleUserName = try container.decodeIfPresent(String.self, forKey: .saleUserName) ?? ""
|
||
photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName) ?? ""
|
||
partnerName = try container.decodeIfPresent(String.self, forKey: .partnerName) ?? ""
|
||
}
|
||
}
|
||
|
||
/// GET /photog/sale-user/invite-info 扫码邀请信息。
|
||
struct SaleUserInviteInfoResponse: Decodable, Equatable {
|
||
let saleUserId: Int
|
||
let isBound: Bool
|
||
let photographerName: String
|
||
let photographerPhone: String
|
||
let salerName: String
|
||
let salerPhone: String
|
||
let shareCode: String
|
||
let subtitle: String
|
||
let title: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case saleUserId = "sale_user_id"
|
||
case isBound = "is_bound"
|
||
case photographerName = "photographer_name"
|
||
case photographerPhone = "photographer_phone"
|
||
case salerName = "saler_name"
|
||
case salerPhone = "saler_phone"
|
||
case shareCode = "share_code"
|
||
case subtitle
|
||
case title
|
||
}
|
||
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
saleUserId = try container.decodeIfPresent(Int.self, forKey: .saleUserId) ?? 0
|
||
isBound = try container.decodeIfPresent(Bool.self, forKey: .isBound) ?? false
|
||
photographerName = try container.decodeIfPresent(String.self, forKey: .photographerName) ?? ""
|
||
photographerPhone = try container.decodeIfPresent(String.self, forKey: .photographerPhone) ?? ""
|
||
salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? ""
|
||
salerPhone = try container.decodeIfPresent(String.self, forKey: .salerPhone) ?? ""
|
||
shareCode = try container.decodeIfPresent(String.self, forKey: .shareCode) ?? ""
|
||
subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) ?? ""
|
||
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
|
||
}
|
||
|
||
init(
|
||
saleUserId: Int = 0,
|
||
isBound: Bool = false,
|
||
photographerName: String = "",
|
||
photographerPhone: String = "",
|
||
salerName: String = "",
|
||
salerPhone: String = "",
|
||
shareCode: String = "",
|
||
subtitle: String = "",
|
||
title: String = ""
|
||
) {
|
||
self.saleUserId = saleUserId
|
||
self.isBound = isBound
|
||
self.photographerName = photographerName
|
||
self.photographerPhone = photographerPhone
|
||
self.salerName = salerName
|
||
self.salerPhone = salerPhone
|
||
self.shareCode = shareCode
|
||
self.subtitle = subtitle
|
||
self.title = title
|
||
}
|
||
}
|
||
|
||
struct SaleUserUpdateRemarkRequest: Encodable {
|
||
let saleUserId: Int
|
||
let photogRemarkName: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case saleUserId = "sale_user_id"
|
||
case photogRemarkName = "photog_remark_name"
|
||
}
|
||
}
|
||
|
||
struct SaleUserCommissionRateSmsRequest: Encodable {
|
||
let phone: String
|
||
}
|
||
|
||
struct SaleUserCommissionRateUpdateRequest: Encodable {
|
||
let saleUserId: Int
|
||
let commissionRate: Int
|
||
let smsCode: String
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case saleUserId = "sale_user_id"
|
||
case commissionRate = "commission_rate"
|
||
case smsCode = "sms_code"
|
||
}
|
||
}
|