Files
suixinkan_ios_new/suixinkan/Features/Orders/Models/OrderModels.swift

525 lines
20 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OrderModels.swift
// suixinkan
//
// Created by Codex on 2026/6/22.
//
import Foundation
/// Tab
enum OrdersEntry: Hashable {
case storeOrders
case verificationOrders
}
///
struct OrderStatusFilter: Identifiable, Hashable {
let id: Int
let title: String
}
///
enum OrderFilters {
static let statusFilters: [OrderStatusFilter] = [
.init(id: 0, title: "全部"),
.init(id: 18, title: "已付定金"),
.init(id: 30, title: "已完成"),
.init(id: 50, title: "已退款"),
.init(id: -1, title: "需要精修"),
.init(id: -2, title: "无需精修")
]
}
///
struct OrderEntity: Decodable, Identifiable, Equatable, Hashable {
var id: String { orderNumber }
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 storeId: Int?
let userId: Int
let projectId: Int
let phone: String
let orderAmount: String
let actualPayAmount: String
let actualRefundAmount: String
let refundAmount: String
let payTypeName: String
let payType: Int
let projectName: String
let orderType: Int
let orderTypeLabel: String
let depositPayTime: String
let createdAt: String
let photoTravel: OrderPhotoTravel?
let isNeedEdit: Bool
let isRefined: Int
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 storeId = "store_id"
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 refundAmount = "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 isNeedEdit
case isRefined = "is_refined"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
remark = try container.decodeLossyString(forKey: .remark)
scenicAreaId = try container.decodeLossyInt(forKey: .scenicAreaId) ?? 0
payTime = try container.decodeLossyString(forKey: .payTime)
completeTime = try container.decodeLossyString(forKey: .completeTime)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
storeId = try container.decodeLossyInt(forKey: .storeId)
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
projectId = try container.decodeLossyInt(forKey: .projectId) ?? 0
phone = try container.decodeLossyString(forKey: .phone)
orderAmount = try container.decodeLossyString(forKey: .orderAmount)
actualPayAmount = try container.decodeLossyString(forKey: .actualPayAmount)
actualRefundAmount = try container.decodeLossyString(forKey: .actualRefundAmount)
refundAmount = try container.decodeLossyString(forKey: .refundAmount)
payTypeName = try container.decodeLossyString(forKey: .payTypeName)
payType = try container.decodeLossyInt(forKey: .payType) ?? 0
projectName = try container.decodeLossyString(forKey: .projectName)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeLossyString(forKey: .orderTypeLabel)
depositPayTime = try container.decodeLossyString(forKey: .depositPayTime)
createdAt = try container.decodeLossyString(forKey: .createdAt)
photoTravel = try container.decodeIfPresent(OrderPhotoTravel.self, forKey: .photoTravel)
isNeedEdit = try container.decodeLossyBool(forKey: .isNeedEdit) ?? true
isRefined = try container.decodeLossyInt(forKey: .isRefined) ?? 0
}
}
///
struct OrderPhotoTravel: Decodable, Equatable, Hashable {
let orderPhotoNum: Int
let orderVideoNum: Int
let checkInTime: String
let retouchGiftPhotoNum: Int
let retouchGiftVideoNum: Int
enum CodingKeys: String, CodingKey {
case orderPhotoNum = "order_photo_num"
case orderVideoNum = "order_video_num"
case checkInTime = "check_in_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.decodeLossyInt(forKey: .orderPhotoNum) ?? 0
orderVideoNum = try container.decodeLossyInt(forKey: .orderVideoNum) ?? 0
checkInTime = try container.decodeLossyString(forKey: .checkInTime)
retouchGiftPhotoNum = try container.decodeLossyInt(forKey: .retouchGiftPhotoNum) ?? 0
retouchGiftVideoNum = try container.decodeLossyInt(forKey: .retouchGiftVideoNum) ?? 0
}
}
///
struct WriteOffOrderItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { orderNumber }
let orderNumber: String
let orderVerificationStatus: String
let payTime: String
let projectName: String
let userPhone: String
let orderAmount: String
let orderStatusName: String
let orderVerificationTime: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderVerificationStatus = "order_verification_status"
case payTime = "pay_time"
case projectName = "project_name"
case userPhone = "user_phone"
case orderAmount = "order_amount"
case orderStatusName = "order_status_name"
case orderVerificationTime = "order_verification_time"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
orderVerificationStatus = try container.decodeLossyString(forKey: .orderVerificationStatus)
payTime = try container.decodeLossyString(forKey: .payTime)
projectName = try container.decodeLossyString(forKey: .projectName)
userPhone = try container.decodeLossyString(forKey: .userPhone)
orderAmount = try container.decodeLossyString(forKey: .orderAmount)
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
orderVerificationTime = try container.decodeLossyString(forKey: .orderVerificationTime)
}
}
///
struct WriteOffRequest: Encodable {
let orderNumber: String
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
}
}
/// push
enum OrdersRoute: Hashable {
case storeDetail(OrderEntity)
case writeOffDetail(WriteOffOrderItem)
}
///
struct StoreOrderDetailResponse: Decodable, Equatable, Hashable {
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: StoreOrderMultiTravelInfo?
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"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeLossyString(forKey: .orderTypeLabel)
createdAt = try container.decodeLossyString(forKey: .createdAt)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
actualPayAmount = try container.decodeLossyString(forKey: .actualPayAmount)
actualRefundAmount = try container.decodeLossyString(forKey: .actualRefundAmount)
payTypeName = try container.decodeLossyString(forKey: .payTypeName)
payTime = try container.decodeLossyString(forKey: .payTime)
completeTime = try container.decodeLossyString(forKey: .completeTime)
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
phone = try container.decodeLossyString(forKey: .phone)
projectId = try container.decodeLossyInt(forKey: .projectId) ?? 0
projectName = try container.decodeLossyString(forKey: .projectName)
remark = try container.decodeLossyString(forKey: .remark)
multiTravel = try container.decodeIfPresent(StoreOrderMultiTravelInfo.self, forKey: .multiTravel)
}
}
///
struct StoreOrderMultiTravelInfo: Decodable, Equatable, Hashable {
let projectInfo: StoreOrderProjectInfo?
let shootingList: [StoreOrderShootingListItem]
enum CodingKeys: String, CodingKey {
case projectInfo = "project_info"
case shootingList = "shooting_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projectInfo = try container.decodeIfPresent(StoreOrderProjectInfo.self, forKey: .projectInfo)
shootingList = try container.decodeIfPresent([StoreOrderShootingListItem].self, forKey: .shootingList) ?? []
}
}
///
struct StoreOrderProjectInfo: Decodable, Equatable, Hashable {
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"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
settleSpotNum = try container.decodeLossyInt(forKey: .settleSpotNum) ?? 0
singleSpotMaterialNum = try container.decodeLossyInt(forKey: .singleSpotMaterialNum) ?? 0
singleSpotPhotoNum = try container.decodeLossyInt(forKey: .singleSpotPhotoNum) ?? 0
singleSpotVideoNum = try container.decodeLossyInt(forKey: .singleSpotVideoNum) ?? 0
}
}
///
struct StoreOrderShootingListItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(scenicSpotId)-\(photogUid)" }
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let staffName: String
let photogName: String
let status: Int
let startAvg: Double
let start: Double
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
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
staffName = try container.decodeLossyString(forKey: .staffName)
photogName = try container.decodeLossyString(forKey: .photogName)
status = try container.decodeLossyInt(forKey: .status) ?? 0
startAvg = try container.decodeLossyDouble(forKey: .startAvg) ?? 0
start = try container.decodeLossyDouble(forKey: .start) ?? 0
}
}
///
struct StoreOrderDetailDisplay: Equatable {
let orderNumber: String
let orderTypeLabel: String
let createdAt: String
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
/// 使
init(item: OrderEntity) {
orderNumber = item.orderNumber
orderTypeLabel = item.orderTypeLabel
createdAt = item.createdAt
orderStatusName = item.orderStatusName
actualPayAmount = item.actualPayAmount
actualRefundAmount = item.actualRefundAmount.isEmpty ? item.refundAmount : item.actualRefundAmount
payTypeName = item.payTypeName
payTime = item.payTime
completeTime = item.completeTime
userId = item.userId
phone = item.phone
projectId = item.projectId
projectName = item.projectName
remark = item.remark
}
/// 使
init(detail: StoreOrderDetailResponse) {
orderNumber = detail.orderNumber
orderTypeLabel = detail.orderTypeLabel
createdAt = detail.createdAt
orderStatusName = detail.orderStatusName
actualPayAmount = detail.actualPayAmount
actualRefundAmount = detail.actualRefundAmount
payTypeName = detail.payTypeName
payTime = detail.payTime
completeTime = detail.completeTime
userId = detail.userId
phone = detail.phone
projectId = detail.projectId
projectName = detail.projectName
remark = detail.remark
}
}
///
enum OrderNumberParser {
/// URL
static func parse(_ raw: String) -> String? {
let text = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { return nil }
if let url = URL(string: text),
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems,
let value = queryItems.first(where: { $0.name.lowercased() == "order_number" })?.value,
!value.isEmpty {
return value
}
let patterns = [
"order[_-]?number[=:]([A-Za-z0-9_-]+)",
"([A-Za-z0-9_-]{8,})"
]
for pattern in patterns {
if let match = firstCapture(in: text, pattern: pattern) {
return match
}
}
if !text.contains(" "), text.count >= 6 {
return text
}
return nil
}
///
private static func firstCapture(in text: String, pattern: String) -> String? {
guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {
return nil
}
let range = NSRange(text.startIndex..<text.endIndex, in: text)
guard let match = regex.firstMatch(in: text, options: [], range: range),
match.numberOfRanges > 1,
let captureRange = Range(match.range(at: 1), in: text) else {
return nil
}
return String(text[captureRange])
}
}
private extension KeyedDecodingContainer {
/// JSON
func decodeLossyString(forKey key: Key) throws -> String {
if let value = try? decodeIfPresent(String.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? "1" : "0"
}
return ""
}
/// IntDoubleBool
func decodeLossyInt(forKey key: Key) throws -> Int? {
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
if let intValue = Int(text) {
return intValue
}
if let doubleValue = Double(text) {
return Int(doubleValue)
}
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return Int(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? 1 : 0
}
return nil
}
/// BoolInt
func decodeLossyBool(forKey key: Key) throws -> Bool? {
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value != 0
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
switch value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "1", "true", "yes":
return true
case "0", "false", "no":
return false
default:
return nil
}
}
return nil
}
/// DoubleInt
func decodeLossyDouble(forKey key: Key) throws -> Double? {
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return Double(value)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
return Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
}
return nil
}
}