Files
suixinkan_uikit/suixinkan/Features/Orders/Models/OrderModels.swift
T

1132 lines
38 KiB
Swift

//
// OrderModels.swift
// suixinkan
//
import Foundation
// MARK: - List Response
/// 订单列表分页响应,字段为 `list`(非 `data`)。
struct OrderListResponse<T: Decodable>: Decodable {
let total: Int
let list: [T]
init(total: Int = 0, list: [T] = []) {
self.total = total
self.list = list
}
}
// MARK: - Photographer Order
struct OrderEntity: Decodable, Equatable {
let photogUid: Int
let orderNumber: String
let remark: String?
let scenicAreaId: Int
let payTime: String
let completeTime: String
let orderStatus: Int
let orderStatusName: String
let userId: Int
let projectId: Int
let phone: String
let orderAmount: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payType: Int
let projectName: String
let orderType: Int
let orderTypeLabel: String
let depositPayTime: String
let createdAt: String
let photoTravel: PhotoTravelEntity?
let refundAmount: String
let multiTravel: MultiTravelEntity?
let isNeedEdit: Bool
let isRefined: Int
let canBindReferral: Int
let showOrderSource: Int
let referralOrderNo: String
let referralSalerName: String
let referralSalerPhotogRemarkName: String
let referralOrder: BoundReferralOrderEntity?
var displayReferralSalerName: String {
OrderDisplayName.saleUser(remark: referralSalerPhotogRemarkName, primary: referralSalerName)
}
enum CodingKeys: String, CodingKey {
case photogUid = "photog_uid"
case orderNumber = "order_number"
case remark
case scenicAreaId = "scenic_area_id"
case payTime = "pay_time"
case completeTime = "complete_time"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case userId = "user_id"
case projectId = "project_id"
case phone
case orderAmount = "order_amount"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case payTypeName = "pay_type_name"
case payType = "pay_type"
case projectName = "project_name"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case depositPayTime = "deposit_pay_time"
case createdAt = "created_at"
case photoTravel = "photo_travel"
case refundAmount = "refund_amount"
case multiTravel = "multi_travel"
case isNeedEdit
case isRefined = "is_refined"
case canBindReferral = "can_bind_referral"
case showOrderSource = "show_order_source"
case referralOrderNo = "referral_order_no"
case referralSalerName = "referral_saler_name"
case referralSalerPhotogRemarkName = "photog_remark_name"
case referralOrder = "referral_order"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
photogUid = try container.decodeIfPresent(Int.self, forKey: .photogUid) ?? 0
orderNumber = try container.decodeIfPresent(String.self, forKey: .orderNumber) ?? ""
remark = try container.decodeIfPresent(String.self, forKey: .remark)
scenicAreaId = try container.decodeIfPresent(Int.self, forKey: .scenicAreaId) ?? 0
payTime = try container.decodeIfPresent(String.self, forKey: .payTime) ?? ""
completeTime = try container.decodeIfPresent(String.self, forKey: .completeTime) ?? ""
orderStatus = try container.decodeIfPresent(Int.self, forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeIfPresent(String.self, forKey: .orderStatusName) ?? ""
userId = try container.decodeIfPresent(Int.self, forKey: .userId) ?? 0
projectId = try container.decodeIfPresent(Int.self, forKey: .projectId) ?? 0
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? ""
orderAmount = try Self.decodeFlexibleString(container: container, key: .orderAmount)
actualPayAmount = try Self.decodeFlexibleString(container: container, key: .actualPayAmount)
actualRefundAmount = try Self.decodeFlexibleString(container: container, key: .actualRefundAmount, defaultValue: "0")
payTypeName = try container.decodeIfPresent(String.self, forKey: .payTypeName) ?? ""
payType = try container.decodeIfPresent(Int.self, forKey: .payType) ?? 0
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) ?? ""
depositPayTime = try container.decodeIfPresent(String.self, forKey: .depositPayTime) ?? ""
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
photoTravel = try Self.decodePhotoTravel(from: container)
refundAmount = try Self.decodeFlexibleString(container: container, key: .refundAmount, defaultValue: "0")
multiTravel = try Self.decodeMultiTravel(from: container)
isNeedEdit = try container.decodeIfPresent(Bool.self, forKey: .isNeedEdit) ?? true
isRefined = try container.decodeIfPresent(Int.self, forKey: .isRefined) ?? 0
canBindReferral = try container.decodeIfPresent(Int.self, forKey: .canBindReferral) ?? 0
showOrderSource = try container.decodeIfPresent(Int.self, forKey: .showOrderSource) ?? 0
referralOrderNo = try container.decodeIfPresent(String.self, forKey: .referralOrderNo) ?? ""
referralSalerName = try container.decodeIfPresent(String.self, forKey: .referralSalerName) ?? ""
referralSalerPhotogRemarkName = try container.decodeIfPresent(String.self, forKey: .referralSalerPhotogRemarkName) ?? ""
referralOrder = try container.decodeIfPresent(BoundReferralOrderEntity.self, forKey: .referralOrder)
}
private static func decodeFlexibleString(
container: KeyedDecodingContainer<CodingKeys>,
key: CodingKeys,
defaultValue: String = ""
) throws -> String {
if let string = try? container.decode(String.self, forKey: key) {
return string
}
if let intValue = try? container.decode(Int.self, forKey: key) {
return String(intValue)
}
if let doubleValue = try? container.decode(Double.self, forKey: key) {
return String(doubleValue)
}
return defaultValue
}
private static func decodePhotoTravel(
from container: KeyedDecodingContainer<CodingKeys>
) throws -> PhotoTravelEntity? {
guard container.contains(.photoTravel), try !container.decodeNil(forKey: .photoTravel) else {
return nil
}
let travel = try container.decode(PhotoTravelEntity.self, forKey: .photoTravel)
return travel.isEmpty ? nil : travel
}
private static func decodeMultiTravel(
from container: KeyedDecodingContainer<CodingKeys>
) throws -> MultiTravelEntity? {
guard container.contains(.multiTravel), try !container.decodeNil(forKey: .multiTravel) else {
return nil
}
let travel = try container.decode(MultiTravelEntity.self, forKey: .multiTravel)
return travel.isEmpty ? nil : travel
}
}
struct BoundReferralOrderEntity: Decodable, Equatable {
let id: Int
let orderNo: String
let saleUserId: Int
let salerName: String
let photogRemarkName: String
let salerPhone: String
let displayText: String
let userMobile: String
let remark: String
let bindOrderNumber: String
let sourceType: Int
let sourceTypeLabel: String
let status: Int
let statusLabel: String
let createdAt: String
var displaySalerName: String {
OrderDisplayName.saleUser(remark: photogRemarkName, primary: salerName)
}
enum CodingKeys: String, CodingKey {
case id
case orderNo = "order_no"
case saleUserId = "sale_user_id"
case salerName = "saler_name"
case photogRemarkName = "photog_remark_name"
case salerPhone = "saler_phone"
case displayText = "display_text"
case userMobile = "user_mobile"
case remark
case bindOrderNumber = "bind_order_number"
case sourceType = "source_type"
case sourceTypeLabel = "source_type_label"
case status
case statusLabel = "status_label"
case createdAt = "created_at"
case saleUser = "sale_user"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
orderNo = try container.decodeIfPresent(String.self, forKey: .orderNo) ?? ""
saleUserId = try container.decodeIfPresent(Int.self, forKey: .saleUserId) ?? 0
salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? ""
salerPhone = try container.decodeIfPresent(String.self, forKey: .salerPhone) ?? ""
displayText = try container.decodeIfPresent(String.self, forKey: .displayText) ?? ""
userMobile = try container.decodeIfPresent(String.self, forKey: .userMobile) ?? ""
remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? ""
bindOrderNumber = try container.decodeIfPresent(String.self, forKey: .bindOrderNumber) ?? ""
sourceType = try container.decodeIfPresent(Int.self, forKey: .sourceType) ?? 0
sourceTypeLabel = try container.decodeIfPresent(String.self, forKey: .sourceTypeLabel) ?? ""
status = try container.decodeIfPresent(Int.self, forKey: .status) ?? 0
statusLabel = try container.decodeIfPresent(String.self, forKey: .statusLabel) ?? ""
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
let saleUser = try container.decodeIfPresent(ReferralSaleUserEntity.self, forKey: .saleUser)
photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName)
?? saleUser?.photogRemarkName
?? saleUser?.displayName
?? ""
}
}
/// `referral_order.sale_user` 嵌套对象,接口字段与顶层可能重复。
struct ReferralSaleUserEntity: Decodable, Equatable {
let photogRemarkName: String
let displayName: String
let salerName: String
enum CodingKeys: String, CodingKey {
case photogRemarkName = "photog_remark_name"
case displayName = "display_name"
case salerName = "saler_name"
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
photogRemarkName = try container.decodeIfPresent(String.self, forKey: .photogRemarkName) ?? ""
let name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
salerName = try container.decodeIfPresent(String.self, forKey: .salerName) ?? name
displayName = try container.decodeIfPresent(String.self, forKey: .displayName) ?? salerName
}
}
struct PhotoTravelEntity: Decodable, Equatable {
let orderPhotoNum: Int
let orderVideoNum: Int
let canCancelOrder: Bool
let needCheckIn: Bool
let checkInTime: String
let canGiftRetouch: Bool
let retouchGiftTime: String
let retouchGiftPhotoNum: Int
let retouchGiftVideoNum: Int
enum CodingKeys: String, CodingKey {
case orderPhotoNum = "order_photo_num"
case orderVideoNum = "order_video_num"
case canCancelOrder = "can_cancel_order"
case needCheckIn = "need_check_in"
case checkInTime = "check_in_time"
case canGiftRetouch = "can_gift_retouch"
case retouchGiftTime = "retouch_gift_time"
case retouchGiftPhotoNum = "retouch_gift_photo_num"
case retouchGiftVideoNum = "retouch_gift_video_num"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderPhotoNum = try container.decodeIfPresent(Int.self, forKey: .orderPhotoNum) ?? 0
orderVideoNum = try container.decodeIfPresent(Int.self, forKey: .orderVideoNum) ?? 0
canCancelOrder = try container.decodeIfPresent(Bool.self, forKey: .canCancelOrder) ?? false
needCheckIn = try container.decodeIfPresent(Bool.self, forKey: .needCheckIn) ?? false
checkInTime = try container.decodeIfPresent(String.self, forKey: .checkInTime) ?? ""
canGiftRetouch = try container.decodeIfPresent(Bool.self, forKey: .canGiftRetouch) ?? false
retouchGiftTime = try container.decodeIfPresent(String.self, forKey: .retouchGiftTime) ?? ""
retouchGiftPhotoNum = try container.decodeIfPresent(Int.self, forKey: .retouchGiftPhotoNum) ?? 0
retouchGiftVideoNum = try container.decodeIfPresent(Int.self, forKey: .retouchGiftVideoNum) ?? 0
}
var isEmpty: Bool {
orderPhotoNum == 0
&& orderVideoNum == 0
&& !canCancelOrder
&& !needCheckIn
&& checkInTime.isEmpty
&& !canGiftRetouch
&& retouchGiftTime.isEmpty
&& retouchGiftPhotoNum == 0
&& retouchGiftVideoNum == 0
}
}
struct MultiTravelEntity: Decodable, Equatable {
let materialList: [MultiTravelMaterialEntity]
enum CodingKeys: String, CodingKey {
case materialList = "material_list"
}
init(materialList: [MultiTravelMaterialEntity] = []) {
self.materialList = materialList
}
var isEmpty: Bool {
materialList.isEmpty
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
materialList = try container.decodeIfPresent([MultiTravelMaterialEntity].self, forKey: .materialList) ?? []
}
}
struct MultiTravelMaterialEntity: Decodable, Equatable {
let fileName: String
let fileUrl: String
let fileType: Int
let fileSize: Int64
let coverUrl: String
enum CodingKeys: String, CodingKey {
case fileName = "file_name"
case fileUrl = "file_url"
case fileType = "file_type"
case fileSize = "file_size"
case coverUrl = "cover_url"
}
}
// MARK: - Store Order
struct StoreOrderListData: Decodable, Equatable {
let total: Int
let list: [StoreOrderItem]
init(total: Int = 0, list: [StoreOrderItem] = []) {
self.total = total
self.list = list
}
}
struct StoreOrderItem: Decodable, Equatable {
let orderNumber: String
let orderType: Int
let orderTypeLabel: String
let createdAt: String
let orderStatus: Int
let orderStatusName: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payTime: String
let completeTime: String
let userId: Int
let phone: String
let projectId: Int
let projectName: String
let remark: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case createdAt = "created_at"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case payTypeName = "pay_type_name"
case payTime = "pay_time"
case completeTime = "complete_time"
case userId = "user_id"
case phone
case projectId = "project_id"
case projectName = "project_name"
case remark
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeIfPresent(String.self, forKey: .orderNumber) ?? ""
orderType = try container.decodeIfPresent(Int.self, forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeIfPresent(String.self, forKey: .orderTypeLabel) ?? ""
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
orderStatus = try container.decodeIfPresent(Int.self, forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeIfPresent(String.self, forKey: .orderStatusName) ?? ""
actualPayAmount = try container.decodeIfPresent(String.self, forKey: .actualPayAmount) ?? ""
actualRefundAmount = try container.decodeIfPresent(String.self, forKey: .actualRefundAmount) ?? ""
payTypeName = try container.decodeIfPresent(String.self, forKey: .payTypeName) ?? ""
payTime = try container.decodeIfPresent(String.self, forKey: .payTime) ?? ""
completeTime = try container.decodeIfPresent(String.self, forKey: .completeTime) ?? ""
userId = try container.decodeIfPresent(Int.self, forKey: .userId) ?? 0
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? ""
projectId = try container.decodeIfPresent(Int.self, forKey: .projectId) ?? 0
projectName = try container.decodeIfPresent(String.self, forKey: .projectName) ?? ""
remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? ""
}
}
enum StoreOrderType {
static let offlineProduct = 4
static let multiTravelShoot = 19
}
// MARK: - Request Bodies
struct CancelRequest: Encodable {
let orderNumber: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
}
}
struct OrderRefundRequest: Encodable {
var orderNumber: String
var refundType: Int
var refundAmount: String
var refundReason: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case refundType = "refund_type"
case refundAmount = "refund_amount"
case refundReason = "refund_reason"
}
}
struct SendGiftRequest: Encodable {
let orderNumber: String
let photoNum: Int
let videoNum: Int
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case photoNum = "photo_num"
case videoNum = "video_num "
}
}
struct StoreOrderRefundRequest: Encodable {
let storeId: Int
let orderNumber: String
let refundType: Int
let refundAmount: String
let refundReason: String
enum CodingKeys: String, CodingKey {
case storeId = "store_id"
case orderNumber = "order_number"
case refundType = "refund_type"
case refundAmount = "refund_amount"
case refundReason = "refund_reason"
}
}
struct StoreOrderVerifyRequest: Encodable {
let method: String
let userId: Int
let orderNumber: String
let time: String
let storeId: Int
enum CodingKeys: String, CodingKey {
case method
case userId = "user_id"
case orderNumber = "order_number"
case time
case storeId = "store_id"
}
init(userId: Int, orderNumber: String, time: String, storeId: Int) {
method = "order_verify"
self.userId = userId
self.orderNumber = orderNumber
self.time = time
self.storeId = storeId
}
}
struct MultiTravelOrderVerifyRequest: Encodable {
let method: String
let orderNumber: String
let userId: Int
let time: Int64
let checkSum: String
let scenicSpotId: Int
enum CodingKeys: String, CodingKey {
case method
case orderNumber = "order_number"
case userId = "user_id"
case time
case checkSum = "check_sum"
case scenicSpotId = "scenic_spot_id"
}
init(orderNumber: String, userId: Int, time: Int64, checkSum: String, scenicSpotId: Int) {
method = "multi_verify"
self.orderNumber = orderNumber
self.userId = userId
self.time = time
self.checkSum = checkSum
self.scenicSpotId = scenicSpotId
}
}
// MARK: - Responses
struct OrderSignInResponse: Decodable, Equatable {
let checkIn: Bool
let orderNumber: String
let projectName: String
let photogName: String
let scenicName: String
let orderAmount: String
let depositAmount: String
let depositPayTime: String
let userPhone: String
enum CodingKeys: String, CodingKey {
case checkIn = "check_in"
case orderNumber = "order_number"
case projectName = "project_name"
case photogName = "photog_name"
case scenicName = "scenic_name"
case orderAmount = "order_amount"
case depositAmount = "deposit_amount"
case depositPayTime = "deposit_pay_time"
case userPhone = "user_phone"
}
}
struct WriteOffOrderResponse: Decodable, Equatable {
let orderVerify: Bool
let orderNumber: String
let projectName: String
let scenicName: String
let orderAmount: String
let payTime: String
let userPhone: String
let verifyTime: String
enum CodingKeys: String, CodingKey {
case orderVerify = "order_verify"
case orderNumber = "order_number"
case projectName = "project_name"
case scenicName = "scenic_name"
case orderAmount = "order_amount"
case payTime = "pay_time"
case userPhone = "user_phone"
case verifyTime = "time"
}
}
struct MultiTravelScenicSpotListResponse: Decodable {
let scenicSpot: [MultiTravelScenicSpotItem]?
enum CodingKeys: String, CodingKey {
case scenicSpot = "scenic_spot"
}
var entityList: [MultiTravelScenicSpotEntity] {
scenicSpot?.map { $0.toEntity() } ?? []
}
}
struct MultiTravelScenicSpotItem: Decodable {
let id: Int
let name: String
let cover: String
let scenicName: String
let scenicId: Int
let distance: Double
enum CodingKeys: String, CodingKey {
case id, name, cover
case scenicName = "scenic_name"
case scenicId = "scenic_id"
case distance
}
func toEntity() -> MultiTravelScenicSpotEntity {
MultiTravelScenicSpotEntity(
scenicSpotId: id,
name: name,
coverUrl: cover,
scenicName: scenicName,
distanceText: Self.formatDistance(distance),
address: scenicName
)
}
private static func formatDistance(_ distance: Double) -> String {
if distance <= 0 { return "当前点位" }
if distance < 1000 { return "\(Int(distance))米" }
let km = distance / 1000
let tenths = Int(km * 10)
return "约\(tenths / 10).\(tenths % 10)公里"
}
}
struct MultiTravelScenicSpotEntity: Equatable {
let scenicSpotId: Int
let name: String
let coverUrl: String
let scenicName: String
let distanceText: String
let address: String
}
struct MultiTravelShootHistoryResponse: Decodable, Equatable {
let projectName: String
let projectType: Int
let projectTypeName: String
let photogSpotList: [PhotogSpotItem]
enum CodingKeys: String, CodingKey {
case projectName = "project_name"
case projectType = "project_type"
case projectTypeName = "project_type_name"
case photogSpotList = "photog_spot_list"
}
}
struct PhotogSpotItem: Decodable, Equatable {
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let photogNickname: String
let photogName: String
let files: [ShootHistoryFileItem]
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
case scenicSpotName = "scenic_spot_name"
case photogNickname = "photog_nickname"
case photogName = "photog_name"
case files
}
var photographerDisplayName: String {
photogNickname.isEmpty ? photogName : photogNickname
}
var imageUrls: [String] {
files.compactMap { file in
if !file.coverUrl.isEmpty { return file.coverUrl }
if file.fileType == 2, !file.fileUrl.isEmpty { return file.fileUrl }
return file.fileUrl.isEmpty ? nil : file.fileUrl
}
}
}
struct ShootHistoryFileItem: Decodable, Equatable {
let fileName: String
let fileUrl: String
let fileType: Int
let fileSize: Int64
let coverUrl: String
let uploadTime: String
enum CodingKeys: String, CodingKey {
case fileName = "file_name"
case fileUrl = "file_url"
case fileType = "file_type"
case fileSize = "file_size"
case coverUrl = "cover_url"
case uploadTime = "upload_time"
}
}
struct StoreOrderDetailData: Decodable, Equatable {
let orderNumber: String
let orderType: Int
let orderTypeLabel: String
let createdAt: String
let orderStatus: Int
let orderStatusName: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payTime: String
let completeTime: String
let userId: Int
let phone: String
let projectId: Int
let projectName: String
let remark: String
let multiTravel: StoreMultiTravelInfo?
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case createdAt = "created_at"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case payTypeName = "pay_type_name"
case payTime = "pay_time"
case completeTime = "complete_time"
case userId = "user_id"
case phone
case projectId = "project_id"
case projectName = "project_name"
case remark
case multiTravel = "multi_travel"
}
}
struct StoreMultiTravelInfo: Decodable, Equatable {
let projectInfo: StoreProjectInfo?
let shootingList: [StoreShootingListItem]
enum CodingKeys: String, CodingKey {
case projectInfo = "project_info"
case shootingList = "shooting_list"
}
}
struct StoreProjectInfo: Decodable, Equatable {
let settleSpotNum: Int
let singleSpotMaterialNum: Int
let singleSpotPhotoNum: Int
let singleSpotVideoNum: Int
enum CodingKeys: String, CodingKey {
case settleSpotNum = "settle_spot_num"
case singleSpotMaterialNum = "single_spot_material_num"
case singleSpotPhotoNum = "single_spot_photo_num"
case singleSpotVideoNum = "single_spot_video_num"
}
}
struct StoreShootingListItem: Decodable, Equatable {
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let staffName: String
let photogName: String
let status: Int
let startAvg: Float?
let start: Float?
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
case scenicSpotName = "scenic_spot_name"
case staffName = "staff_name"
case photogName = "photog_name"
case status
case startAvg = "start_avg"
case start
}
var displayStaffName: String {
staffName.isEmpty ? photogName : staffName
}
}
// MARK: - Order Source
struct CooperativeSalerListResponse: Decodable {
let total: Int
let list: [CooperativeSalerEntity]
}
struct CooperativeSalerEntity: Decodable, Equatable {
let saleUserId: Int
let bindingId: Int
let name: String
let salerName: String
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"
case bindingId = "binding_id"
case name
case salerName = "saler_name"
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
)
}
}
/// 获客员分成比例修改记录分页响应。
struct CommissionRateLogListResponse: Decodable, Equatable {
let list: [CommissionRateLogEntity]
let total: Int
let page: Int
let pageSize: Int
enum CodingKeys: String, CodingKey {
case list
case total
case page
case pageSize = "page_size"
}
}
/// 单条获客员分成比例修改记录。
struct CommissionRateLogEntity: Decodable, Equatable, Hashable {
let id: Int
let bindingId: Int
let saleUserId: Int
let storeUserId: Int
let beforeRate: Int
let beforeRateLabel: String
let afterRate: Int
let afterRateLabel: String
let operatorType: String
let operatorId: Int
let operatorName: String
let remark: String
let createdAt: String
enum CodingKeys: String, CodingKey {
case id
case bindingId = "binding_id"
case saleUserId = "sale_user_id"
case storeUserId = "store_user_id"
case beforeRate = "before_rate"
case beforeRateLabel = "before_rate_label"
case afterRate = "after_rate"
case afterRateLabel = "after_rate_label"
case operatorType = "operator_type"
case operatorId = "operator_id"
case operatorName = "operator_name"
case remark
case createdAt = "created_at"
}
/// 修改前比例展示文本,优先使用服务端 label。
var displayBeforeRate: String {
let label = beforeRateLabel.trimmingCharacters(in: .whitespacesAndNewlines)
return label.isEmpty ? "\(beforeRate)%" : label
}
/// 修改后比例展示文本,优先使用服务端 label。
var displayAfterRate: String {
let label = afterRateLabel.trimmingCharacters(in: .whitespacesAndNewlines)
return label.isEmpty ? "\(afterRate)%" : label
}
/// 修改时间展示文本。
var metadataLine: String {
formattedCreatedAt
}
/// 去除首尾空白后的备注;空备注不展示。
var displayRemark: String? {
let value = remark.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}
private var formattedCreatedAt: String {
let value = createdAt.trimmingCharacters(in: .whitespacesAndNewlines)
guard !value.isEmpty else { return "—" }
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
for format in ["yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm"] {
formatter.dateFormat = format
if let date = formatter.date(from: value) {
formatter.dateFormat = "yyyy-MM-dd HH:mm"
return formatter.string(from: date)
}
}
return value
}
}
struct ReferralOrderListResponse: Decodable {
let total: Int
let list: [ReferralOrderEntity]
}
struct ReferralOrderEntity: Decodable, Equatable {
let id: Int
let referralOrderNo: String
let orderNo: String
let userMobile: String
let remark: String
let createdAt: String
let status: Int
let statusLabel: String
let images: [String]
var displayReferralOrderNo: String { referralOrderNo.isEmpty ? orderNo : referralOrderNo }
enum CodingKeys: String, CodingKey {
case id
case referralOrderNo = "referral_order_no"
case orderNo = "order_no"
case userMobile = "user_mobile"
case remark
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)) ?? []
}
}
// MARK: - Filters
enum PhotographerOrderFilter: CaseIterable {
case all
case paidDeposit
case completed
case refunded
case needsRefinement
case noRefinement
var label: String {
switch self {
case .all: "全部"
case .paidDeposit: "已付定金"
case .completed: "已完成"
case .refunded: "已退款"
case .needsRefinement: "需要精修"
case .noRefinement: "无需精修"
}
}
var tabIndex: Int {
switch self {
case .all, .needsRefinement, .noRefinement: 0
case .paidDeposit: 18
case .completed: 30
case .refunded: 50
}
}
var rTabIndex: Int {
switch self {
case .needsRefinement: 1
case .noRefinement: 2
default: 0
}
}
static func from(tabIndex: Int, rTabIndex: Int) -> PhotographerOrderFilter {
switch (tabIndex, rTabIndex) {
case (18, 0): .paidDeposit
case (30, 0): .completed
case (50, 0): .refunded
case (0, 1): .needsRefinement
case (0, 2): .noRefinement
default: .all
}
}
}
enum StoreOrderFilter: CaseIterable {
case all
case paid
case completed
case refunded
var label: String {
switch self {
case .all: "全部"
case .paid: "已支付"
case .completed: "已完成"
case .refunded: "已退款"
}
}
var tabIndex: Int {
switch self {
case .all: 0
case .paid: 18
case .completed: 30
case .refunded: 50
}
}
/// UI tab 18 映射 API order_status=20。
var apiOrderStatus: Int {
switch self {
case .paid: 20
default: tabIndex
}
}
static func from(tabIndex: Int) -> StoreOrderFilter {
switch tabIndex {
case 18: .paid
case 30: .completed
case 50: .refunded
default: .all
}
}
}
enum OrderTypeCode {
static let photographerFollow = 11
static let photographerFollowOfflineScan = 14
static let multiTravel = 19
}
enum OrderDisplayName {
static func saleUser(remark: String, primary: String, fallback: String = "") -> String {
if !remark.isEmpty { return remark }
if !primary.isEmpty { return primary }
return fallback
}
}
enum OrderListQueryBuilder {
static let photographerPageSize = 10
static let storePageSize = 20
static func orderStatusQuery(tabIndex: Int) -> String? {
tabIndex == 0 ? nil : String(tabIndex)
}
static func isRefinedQuery(rTabIndex: Int) -> Int? {
rTabIndex == 0 ? nil : rTabIndex
}
static func storeOrderStatusQuery(tabIndex: Int) -> Int? {
let status = StoreOrderFilter.from(tabIndex: tabIndex).apiOrderStatus
return status == 0 ? nil : status
}
}