1132 lines
38 KiB
Swift
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
|
|
}
|
|
}
|