Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fe7dcc6
Expand POSOrder and POSOrderItem for details view needs
staskus Aug 29, 2025
8943b05
Allow setting bottom content on POSPageHeaderView
staskus Sep 1, 2025
fce7b2d
Create PointOfSaleOrderDetailsViewHelper to handle calculations and f…
staskus Sep 1, 2025
3abcc28
Add PointOfSaleOrderDetailsViewHelperTests
staskus Sep 1, 2025
c8dad49
Improve PointOfSaleOrderDetailsView to wireframe designs
staskus Sep 1, 2025
1dfc8b1
Manage selected order within orders controller
staskus Sep 2, 2025
0b94d0d
Add a simple loading view for Order Details
staskus Sep 2, 2025
f969b12
Move PointOfSaleOrderBadgeView into a separate view to reuse
staskus Sep 2, 2025
eb02d0c
Merge branch 'trunk' into woomob-1138-woo-poshistorical-orders-order-…
staskus Sep 2, 2025
d4012be
Remove unused code
staskus Sep 2, 2025
4bc8c86
Fix warning: Backward matching of the unlabeled trailing closure is d…
staskus Sep 3, 2025
ffdc18b
Rename OrdersViewState to POSOrdersViewState
staskus Sep 3, 2025
a87b614
Remove PointOfSaleOrderDetailsViewHelper
staskus Sep 3, 2025
45b67cd
Expand Order+CurrencyFormattedValues with ability to inject currency …
staskus Sep 3, 2025
50bf66e
Create POSOrderMapper to set formatted and calculated totals on POSOrder
staskus Sep 3, 2025
1eec56c
Use formatted values from order in list and details view
staskus Sep 3, 2025
3c3694b
Update PreviewHelpers.swift
staskus Sep 3, 2025
f39d665
Update tests
staskus Sep 3, 2025
2663256
Remove unused code
staskus Sep 4, 2025
4ea5462
Create POSOrderMapperTests
staskus Sep 4, 2025
3c8b718
Remove errors when fetching images
staskus Sep 4, 2025
ffc3a14
Update PointOfSaleOrderDetailsView.swift
staskus Sep 4, 2025
dc6e7c7
Remove unused code
staskus Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 18 additions & 38 deletions Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,67 +11,47 @@ public struct POSOrder: Equatable, Hashable {
public let number: String
public let dateCreated: Date
public let status: OrderStatusEnum
public let total: String
public let formattedTotal: String
public let formattedSubtotal: String
public let customerEmail: String?
public let paymentMethodID: String
public let paymentMethodTitle: String
public let lineItems: [POSOrderItem]
public let refunds: [POSOrderRefund]
public let currency: String
public let currencySymbol: String
public let formattedDiscountTotal: String?
public let formattedTotalTax: String
public let formattedPaymentTotal: String
public let formattedNetAmount: String?

public init(id: Int64,
number: String,
dateCreated: Date,
status: OrderStatusEnum,
total: String,
formattedTotal: String,
formattedSubtotal: String,
customerEmail: String? = nil,
paymentMethodID: String,
paymentMethodTitle: String,
lineItems: [POSOrderItem] = [],
refunds: [POSOrderRefund] = [],
currency: String,
currencySymbol: String) {
formattedTotalTax: String,
formattedDiscountTotal: String?,
formattedPaymentTotal: String,
formattedNetAmount: String? = nil) {
self.id = id
self.number = number
self.dateCreated = dateCreated
self.status = status
self.total = total
self.formattedTotal = formattedTotal
self.formattedSubtotal = formattedSubtotal
self.customerEmail = customerEmail
self.paymentMethodID = paymentMethodID
self.paymentMethodTitle = paymentMethodTitle
self.lineItems = lineItems
self.refunds = refunds
self.currency = currency
self.currencySymbol = currencySymbol
}
}

// MARK: - Conversion from NetworkingCore.Order
public extension POSOrder {
init(from order: NetworkingCore.Order) {
// Extract customer email from billing address
let customerEmail = order.billingAddress?.email

// Convert line items to POS format
let posLineItems = order.items.map { POSOrderItem(from: $0) }

// Convert refunds to POS format
let posRefunds = order.refunds.map { POSOrderRefund(from: $0) }

self.init(
id: order.orderID,
number: order.number,
dateCreated: order.dateCreated,
status: order.status,
total: order.total,
customerEmail: customerEmail,
paymentMethodID: order.paymentMethodID,
paymentMethodTitle: order.paymentMethodTitle,
lineItems: posLineItems,
refunds: posRefunds,
currency: order.currency,
currencySymbol: order.currencySymbol
)
self.formattedTotalTax = formattedTotalTax
self.formattedDiscountTotal = formattedDiscountTotal
self.formattedPaymentTotal = formattedPaymentTotal
self.formattedNetAmount = formattedNetAmount
}
}
33 changes: 17 additions & 16 deletions Modules/Sources/Yosemite/PointOfSale/OrderList/POSOrderItem.swift
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import Foundation
import struct NetworkingCore.OrderItem

public struct POSOrderItem: Equatable, Hashable {
public let itemID: Int64
public let name: String
// periphery:ignore - Will be used for images
public let productID: Int64
// periphery:ignore - Will be used for images
public let variationID: Int64
public let quantity: Decimal
public let total: String
public let formattedPrice: String
public let formattedTotal: String
public let attributes: [OrderItemAttribute]

public init(itemID: Int64,
name: String,
productID: Int64,
variationID: Int64,
quantity: Decimal,
total: String) {
formattedPrice: String,
formattedTotal: String,
attributes: [OrderItemAttribute]) {
self.itemID = itemID
self.name = name
self.productID = productID
self.variationID = variationID
self.quantity = quantity
self.total = total
}
}

// MARK: - Conversion from NetworkingCore.OrderItem
public extension POSOrderItem {
init(from orderItem: OrderItem) {
self.init(
itemID: orderItem.itemID,
name: orderItem.name,
quantity: orderItem.quantity,
total: orderItem.total
)
self.formattedPrice = formattedPrice
self.formattedTotal = formattedTotal
self.attributes = attributes
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Foundation
import class WooFoundationCore.CurrencyFormatter
import struct NetworkingCore.Order
import struct NetworkingCore.OrderItem
import struct NetworkingCore.OrderItemAttribute
import struct NetworkingCore.OrderRefundCondensed

struct POSOrderMapper {
private let currencyFormatter: CurrencyFormatter

init(currencyFormatter: CurrencyFormatter) {
self.currencyFormatter = currencyFormatter
}

func map(order: NetworkingCore.Order) -> POSOrder {
let customerEmail = order.billingAddress?.email

let posLineItems = order.items.map { map(orderItem: $0, currency: order.currency) }

let posRefunds = order.refunds.map { map(orderRefund: $0, currency: order.currency) }

let formattedDiscountTotal: String? = {
guard let discountTotalValue = Double(order.discountTotal), discountTotalValue > 0 else {
return nil
}
return currencyFormatter.formatAmount(order.discountTotal, with: order.currency, isNegative: true) ?? ""
}()

let formattedNetAmount: String? = {
guard !order.refunds.isEmpty else {
return nil
}
return order.netAmount(currencyFormatter: currencyFormatter)
}()

return POSOrder(
id: order.orderID,
number: order.number,
dateCreated: order.dateCreated,
status: order.status,
formattedTotal: currencyFormatter.formatAmount(order.total, with: order.currency) ?? "",
formattedSubtotal: order.subtotalValue(currencyFormatter: currencyFormatter),
customerEmail: customerEmail,
paymentMethodID: order.paymentMethodID,
paymentMethodTitle: order.paymentMethodTitle,
lineItems: posLineItems,
refunds: posRefunds,
formattedTotalTax: currencyFormatter.formatAmount(order.totalTax, with: order.currency) ?? "",
formattedDiscountTotal: formattedDiscountTotal,
formattedPaymentTotal: order.paymentTotal(currencyFormatter: currencyFormatter),
formattedNetAmount: formattedNetAmount
)
}

private func map(orderItem: NetworkingCore.OrderItem, currency: String) -> POSOrderItem {
return POSOrderItem(
itemID: orderItem.itemID,
name: orderItem.name,
productID: orderItem.productID,
variationID: orderItem.variationID,
quantity: orderItem.quantity,
formattedPrice: currencyFormatter.formatAmount(orderItem.price, with: currency) ?? "",
formattedTotal: currencyFormatter.formatAmount(orderItem.total, with: currency) ?? "",
attributes: orderItem.attributes
)
}

private func map(orderRefund: NetworkingCore.OrderRefundCondensed, currency: String) -> POSOrderRefund {
return POSOrderRefund(
refundID: orderRefund.refundID,
formattedTotal: currencyFormatter.formatAmount(orderRefund.total, with: currency) ?? "",
reason: orderRefund.reason
)
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
import Foundation
import struct NetworkingCore.OrderRefundCondensed
import class WooFoundationCore.CurrencyFormatter

public struct POSOrderRefund: Equatable, Hashable {
public let refundID: Int64
public let total: String
public let formattedTotal: String
public let reason: String?

public init(refundID: Int64,
total: String,
formattedTotal: String,
reason: String? = nil) {
self.refundID = refundID
self.total = total
self.formattedTotal = formattedTotal
self.reason = reason
}
}

// MARK: - Conversion from NetworkingCore.OrderRefundCondensed
public extension POSOrderRefund {
init(from refund: OrderRefundCondensed) {
self.init(
refundID: refund.refundID,
total: refund.total,
reason: refund.reason
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Foundation
import class Networking.AlamofireNetwork
import class Networking.OrdersRemote
import class WooFoundationCore.CurrencyFormatter

public protocol PointOfSaleOrderListFetchStrategyFactoryProtocol {
func defaultStrategy() -> PointOfSaleOrderListFetchStrategy
Expand All @@ -9,16 +10,24 @@ public protocol PointOfSaleOrderListFetchStrategyFactoryProtocol {
public final class PointOfSaleOrderListFetchStrategyFactory: PointOfSaleOrderListFetchStrategyFactoryProtocol {
private let siteID: Int64
private let ordersRemote: OrdersRemote
private let currencyFormatter: CurrencyFormatter

public init(siteID: Int64,
credentials: Credentials?) {
credentials: Credentials?,
currencyFormatter: CurrencyFormatter) {
self.siteID = siteID
let network = AlamofireNetwork(credentials: credentials)
self.ordersRemote = OrdersRemote(network: network)
self.currencyFormatter = currencyFormatter
}

public func defaultStrategy() -> PointOfSaleOrderListFetchStrategy {
PointOfSaleDefaultOrderListFetchStrategy(orderListService: PointOfSaleOrderListService(siteID: siteID,
ordersRemote: ordersRemote))
PointOfSaleDefaultOrderListFetchStrategy(
orderListService: PointOfSaleOrderListService(
siteID: siteID,
ordersRemote: ordersRemote,
currencyFormatter: currencyFormatter
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import enum Alamofire.AFError
import struct NetworkingCore.PagedItems
import struct NetworkingCore.Order
import protocol NetworkingCore.POSOrdersRemoteProtocol
import class WooFoundationCore.CurrencyFormatter

public final class PointOfSaleOrderListService: PointOfSaleOrderListServiceProtocol {
private let ordersRemote: POSOrdersRemoteProtocol
private let siteID: Int64
private let mapper: POSOrderMapper

public init(siteID: Int64, ordersRemote: POSOrdersRemoteProtocol) {
public init(
siteID: Int64,
ordersRemote: POSOrdersRemoteProtocol,
currencyFormatter: CurrencyFormatter
) {
self.siteID = siteID
self.ordersRemote = ordersRemote
self.mapper = POSOrderMapper(currencyFormatter: currencyFormatter)
}

public func providePointOfSaleOrders(pageNumber: Int = 1) async throws -> PagedItems<POSOrder> {
Expand All @@ -26,7 +33,7 @@ public final class PointOfSaleOrderListService: PointOfSaleOrderListServiceProto
}

// Convert Order objects to POSOrder objects
let posOrders = pagedOrders.items.map { POSOrder(from: $0) }
let posOrders = pagedOrders.items.map { mapper.map(order: $0) }

return .init(items: posOrders,
hasMorePages: pagedOrders.hasMorePages,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,49 @@ public extension Order {
}

var netAmount: String? {
guard let netDecimal = calculateNetAmount() else {
netAmount(currencyFormatter: currencyFormatter)
}

var totalValue: String {
totalValue(currencyFormatter: currencyFormatter)
}

var subtotal: Decimal {
let subtotal = items.reduce(.zero) { (output, item) in
let itemSubtotal = Decimal(string: item.subtotal) ?? .zero
return output + itemSubtotal
}

return subtotal
}

func netAmount(currencyFormatter: CurrencyFormatter) -> String? {
guard let netDecimal = calculateNetAmount(currencyFormatter: currencyFormatter) else {
return nil
}

return currencyFormatter.formatAmount(netDecimal, with: currency)
}

var paymentTotal: String {
func paymentTotal(currencyFormatter: CurrencyFormatter) -> String {
if datePaid == nil {
return currencyFormatter.formatAmount("0.00", with: currency) ?? String()
}

return totalValue
return totalValue(currencyFormatter: currencyFormatter)
}

var totalValue: String {
func totalValue(currencyFormatter: CurrencyFormatter) -> String {
return currencyFormatter.formatAmount(total, with: currency) ?? String()
}

private func calculateNetAmount() -> NSDecimalNumber? {
func subtotalValue(currencyFormatter: CurrencyFormatter) -> String {
let subAmount = NSDecimalNumber(decimal: subtotal).stringValue

return currencyFormatter.formatAmount(subAmount, with: currency) ?? String()
}

private func calculateNetAmount(currencyFormatter: CurrencyFormatter) -> NSDecimalNumber? {
guard let orderTotal = currencyFormatter.convertToDecimal(total) else {
return .zero
}
Expand Down
Loading