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,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)
}
}