feat(gift): 선물 표시 정책을 추가한다

This commit is contained in:
Yu Sung
2026-10-08 12:32:44 +09:00
parent 9b7acc905a
commit 07afa85e21
2 changed files with 269 additions and 0 deletions
@@ -0,0 +1,105 @@
import Foundation
enum GiftDirection: String, Decodable {
case sent = "SENT"
case received = "RECEIVED"
}
enum GiftStatus: String, Decodable, CaseIterable {
case received = "RECEIVED"
case trackingRegistered = "TRACKING_REGISTERED"
case arrivedAtMailbox = "ARRIVED_AT_MAILBOX"
case inspectionCompleted = "INSPECTION_COMPLETED"
case delivered = "DELIVERED"
case undeliverable = "UNDELIVERABLE"
case canceled = "CANCELED"
}
enum GiftProgressStep: String, CaseIterable {
case received = "RECEIVED"
case trackingRegistered = "TRACKING_REGISTERED"
case arrivedAtMailbox = "ARRIVED_AT_MAILBOX"
case inspectionCompleted = "INSPECTION_COMPLETED"
case delivered = "DELIVERED"
var title: String {
switch self {
case .received:
return "신청 접수"
case .trackingRegistered:
return "발송 확인"
case .arrivedAtMailbox:
return "사서함 도착"
case .inspectionCompleted:
return "검수 완료"
case .delivered:
return "전달 완료"
}
}
}
enum GiftSize: String, Decodable {
case small = "SMALL"
case medium = "MEDIUM"
case large = "LARGE"
}
struct GiftListStatusDisplayModel {
let title: String
let subtitle: String?
}
struct GiftListCardDisplayModel {
let applicationNo: String
let direction: GiftDirection
let status: GiftStatus
let counterpartNickname: String
let counterpartProfileImageUrl: String?
let categoryName: String
let sizeName: String
let createdDateText: String?
let statusDisplay: GiftListStatusDisplayModel
}
struct GiftProgressDisplayModel {
let step: GiftProgressStep
let occurredDateText: String?
}
struct GiftAddressDisplayModel {
let name: String
let phoneNumber: String
let address: String
}
struct SentGiftDetailDisplayModel {
let status: GiftStatus
let statusName: String
let undeliverableReason: String?
let recipientCreatorNickname: String?
let sizeName: String
let categoryName: String
let applicationNo: String
let paidCan: Int?
let tracking: String?
let shippingRequestedDateText: String?
let applicationDateText: String?
let canceledDateText: String?
let senderInfo: GiftAddressDisplayModel?
let mailbox: GiftAddressDisplayModel?
let trackingRequired: Bool
let progress: [GiftProgressDisplayModel]
}
struct ReceivedGiftDetailDisplayModel {
let status: GiftStatus
let statusName: String
let undeliverableReason: String?
let senderNickname: String?
let sizeName: String
let categoryName: String
let shippingRequestedDateText: String?
let recipientAddressRequired: Bool
let recipientAddress: GiftAddressDisplayModel?
let progress: [GiftProgressDisplayModel]
}
@@ -0,0 +1,164 @@
import Foundation
enum GiftDateFormatter {
static func yyyyMMdd(fromUTC text: String?) -> String? {
format(text, format: "yyyy.MM.dd")
}
static func mmDD(fromUTC text: String?) -> String? {
format(text, format: "MM.dd")
}
private static func format(_ text: String?, format: String) -> String? {
guard let text, let date = DateParser.parse(text) else { return nil }
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = .autoupdatingCurrent
formatter.dateFormat = format
return formatter.string(from: date)
}
}
enum GiftDisplayPolicy {
static func listCard(from item: GiftListItemResponse) -> GiftListCardDisplayModel? {
guard let direction = GiftDirection(rawValue: item.direction),
let status = GiftStatus(rawValue: item.status) else {
return nil
}
return GiftListCardDisplayModel(
applicationNo: item.applicationNo,
direction: direction,
status: status,
counterpartNickname: item.counterpartNickname,
counterpartProfileImageUrl: item.counterpartProfileImageUrl,
categoryName: item.categoryName,
sizeName: item.sizeName,
createdDateText: GiftDateFormatter.yyyyMMdd(fromUTC: item.createdAt),
statusDisplay: listStatus(direction: direction, status: status, statusName: item.statusName)
)
}
static func sentDetail(from detail: GiftDetailResponse) -> SentGiftDetailDisplayModel? {
guard let status = status(from: detail, expectedDirection: .sent) else { return nil }
return SentGiftDetailDisplayModel(
status: status,
statusName: detail.statusName,
undeliverableReason: detail.delivery.undeliverableReason,
recipientCreatorNickname: detail.giftInfo.recipientCreatorNickname,
sizeName: detail.giftInfo.sizeName,
categoryName: detail.giftInfo.categoryName,
applicationNo: detail.applicationNo,
paidCan: detail.giftInfo.paidCan,
tracking: detail.giftInfo.tracking,
shippingRequestedDateText: GiftDateFormatter.yyyyMMdd(fromUTC: detail.giftInfo.shippingRequestedAt),
applicationDateText: GiftDateFormatter.yyyyMMdd(
fromUTC: detail.statusTimeline.first { $0.status == GiftStatus.received.rawValue }?.occurredAt
),
canceledDateText: GiftDateFormatter.yyyyMMdd(fromUTC: detail.delivery.canceledAt),
senderInfo: detail.senderInfo.map(GiftAddressDisplayModel.init),
mailbox: detail.mailbox.map(GiftAddressDisplayModel.init),
trackingRequired: detail.trackingRequired,
progress: progress(from: detail.statusTimeline)
)
}
static func receivedDetail(from detail: GiftDetailResponse) -> ReceivedGiftDetailDisplayModel? {
guard let status = status(from: detail, expectedDirection: .received) else { return nil }
return ReceivedGiftDetailDisplayModel(
status: status,
statusName: detail.statusName,
undeliverableReason: detail.delivery.undeliverableReason,
senderNickname: detail.giftInfo.senderNickname,
sizeName: detail.giftInfo.sizeName,
categoryName: detail.giftInfo.categoryName,
shippingRequestedDateText: GiftDateFormatter.yyyyMMdd(fromUTC: detail.giftInfo.shippingRequestedAt),
recipientAddressRequired: status == .undeliverable ? false : detail.recipientAddressRequired,
recipientAddress: status == .undeliverable ? nil : detail.recipientAddress.map(GiftAddressDisplayModel.init),
progress: progress(from: detail.statusTimeline)
)
}
static func sizeDescription(for sizeCode: String) -> String? {
guard let size = GiftSize(rawValue: sizeCode) else { return nil }
switch size {
case .small:
return "세 변의 합 100cm 이하 · 5kg 이하"
case .medium:
return "세 변의 합 120cm 이하 · 15kg 이하"
case .large:
return "세 변의 합 160cm 이하 · 20kg 이하"
}
}
private static func listStatus(
direction: GiftDirection,
status: GiftStatus,
statusName: String
) -> GiftListStatusDisplayModel {
switch (direction, status) {
case (.sent, .received):
return GiftListStatusDisplayModel(title: "운송장 등록 필요", subtitle: nil)
case (.sent, .trackingRegistered):
return GiftListStatusDisplayModel(title: "발송 확인", subtitle: nil)
case (.sent, .arrivedAtMailbox):
return GiftListStatusDisplayModel(title: "사서함 도착", subtitle: nil)
case (.sent, .inspectionCompleted):
return GiftListStatusDisplayModel(title: "선물 검수 완료", subtitle: nil)
case (.sent, .delivered):
return GiftListStatusDisplayModel(title: "전달 완료", subtitle: nil)
case (.sent, .undeliverable):
return GiftListStatusDisplayModel(title: "전달 불가", subtitle: nil)
case (.sent, .canceled):
return GiftListStatusDisplayModel(title: "신청 취소", subtitle: nil)
case (.received, .trackingRegistered):
return GiftListStatusDisplayModel(title: "배송지 입력 필요", subtitle: "전달 예정")
case (.received, .arrivedAtMailbox):
return GiftListStatusDisplayModel(title: "선물 검수", subtitle: "선물 확인 중")
case (.received, .inspectionCompleted):
return GiftListStatusDisplayModel(title: "선물 검수 완료", subtitle: "배송 중")
case (.received, .delivered):
return GiftListStatusDisplayModel(title: "전달 완료", subtitle: nil)
case (.received, .undeliverable):
return GiftListStatusDisplayModel(title: "전달 불가", subtitle: nil)
default:
return GiftListStatusDisplayModel(title: statusName, subtitle: nil)
}
}
private static func status(
from detail: GiftDetailResponse,
expectedDirection: GiftDirection
) -> GiftStatus? {
guard GiftDirection(rawValue: detail.direction) == expectedDirection else { return nil }
return GiftStatus(rawValue: detail.status)
}
private static func progress(from timeline: [GiftStatusTimelineResponse]) -> [GiftProgressDisplayModel] {
GiftProgressStep.allCases.map { step in
let occurredAt = timeline.first { $0.status == step.rawValue }?.occurredAt
return GiftProgressDisplayModel(
step: step,
occurredDateText: GiftDateFormatter.mmDD(fromUTC: occurredAt)
)
}
}
}
private extension GiftAddressDisplayModel {
init(_ senderInfo: GiftSenderInfoResponse) {
self.init(name: senderInfo.name, phoneNumber: senderInfo.phoneNumber, address: senderInfo.address)
}
init(_ recipientAddress: GiftRecipientAddressResponse) {
self.init(name: recipientAddress.name, phoneNumber: recipientAddress.phoneNumber, address: recipientAddress.address)
}
init(_ mailbox: GiftMailboxResponse) {
self.init(name: mailbox.name, phoneNumber: mailbox.phoneNumber, address: mailbox.address)
}
}