Skip to content

[Shipping labels] Add endpoint to fetch shipments #13995

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"request": {
"method": "GET",
"urlPathPattern": "/rest/v1.1/jetpack-blogs/161477129/rest-api/",
"queryParameters": {
"json": {
"equalTo": "true"
},
"path": {
"matches": "/wcshipping/v1/config/label-purchase/2201(.*)"
},
"locale": {
"matches": "(.*)"
}
}
},
"response": {
"status": 200,
"jsonBody": {
"success": true,
"config": {
"order": {},
"accountSettings": {},
"packagesSettings": {
"schema": {
"custom": {},
"predefined": {}
},
"packages": {
"custom": {},
"predefined": {}
}
},
"shippingLabelData": {},
"continents": {},
"eu_countries": {},
"items": {},
"is_destination_verified": {},
"is_origin_verified": {},
"shipments": {},
"origin_addresses": {}
}
},
"headers": {
"Content-Type": "application/json",
"Connection": "keep-alive"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import com.woocommerce.android.datastore.DataStoreType.COUPONS
import com.woocommerce.android.datastore.DataStoreType.DASHBOARD_STATS
import com.woocommerce.android.datastore.DataStoreType.LAST_UPDATE
import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_ADDRESS
import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_CONFIGURATION
import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_CONFIG
import com.woocommerce.android.datastore.DataStoreType.SHIPPING_LABEL_STORE_OPTIONS
import com.woocommerce.android.datastore.DataStoreType.SITE_PICKER_WOO_VISIBLE_SITES
import com.woocommerce.android.datastore.DataStoreType.TOP_PERFORMER_PRODUCTS
import com.woocommerce.android.datastore.DataStoreType.TRACKER
Expand Down Expand Up @@ -182,8 +183,26 @@ class DataStoreModule {

@Provides
@Singleton
@DataStoreQualifier(SHIPPING_LABEL_CONFIGURATION)
fun provideShippingLabelConfigurationDataStore(
@DataStoreQualifier(SHIPPING_LABEL_CONFIG)
fun provideShippingLabelConfigDataStore(
appContext: Context,
crashLogging: CrashLogging,
@AppCoroutineScope appCoroutineScope: CoroutineScope
) = PreferenceDataStoreFactory.create(
produceFile = { appContext.preferencesDataStoreFile("shipping_label_config") },
corruptionHandler = ReplaceFileCorruptionHandler {
crashLogging.recordEvent(
"Corrupted data store. DataStore Type: ${SHIPPING_LABEL_CONFIG.name}"
)
emptyPreferences()
},
scope = CoroutineScope(appCoroutineScope.coroutineContext + Dispatchers.IO)
)

@Provides
@Singleton
@DataStoreQualifier(SHIPPING_LABEL_STORE_OPTIONS)
fun provideShippingLabelStoreOptionsDataStore(
appContext: Context,
crashLogging: CrashLogging,
@AppCoroutineScope appCoroutineScope: CoroutineScope
Expand All @@ -193,7 +212,7 @@ class DataStoreModule {
},
corruptionHandler = ReplaceFileCorruptionHandler {
crashLogging.recordEvent(
"Corrupted data store. DataStore Type: ${SHIPPING_LABEL_CONFIGURATION.name}"
"Corrupted data store. DataStore Type: ${SHIPPING_LABEL_STORE_OPTIONS.name}"
)
emptyPreferences()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum class DataStoreType {
COUPONS,
LAST_UPDATE,
SITE_PICKER_WOO_VISIBLE_SITES,
SHIPPING_LABEL_CONFIGURATION,
SHIPPING_LABEL_CONFIG,
SHIPPING_LABEL_STORE_OPTIONS,
SHIPPING_LABEL_ADDRESS
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import com.woocommerce.android.ui.orders.creation.shipping.GetShippingMethodsWit
import com.woocommerce.android.ui.orders.creation.shipping.RefreshShippingMethods
import com.woocommerce.android.ui.orders.creation.shipping.ShippingLineDetails
import com.woocommerce.android.ui.orders.creation.shipping.ShippingMethodsRepository
import com.woocommerce.android.ui.orders.wooshippinglabels.networking.WooShippingLabelRepository
import com.woocommerce.android.ui.payments.cardreader.onboarding.CardReaderFlowParam
import com.woocommerce.android.ui.payments.cardreader.payment.CardReaderPaymentCollectibilityChecker
import com.woocommerce.android.ui.payments.receipt.PaymentReceiptHelper
Expand Down Expand Up @@ -108,6 +109,7 @@ class OrderDetailViewModel @Inject constructor(
private val paymentsFlowTracker: PaymentsFlowTracker,
private val tracker: OrderDetailTracker,
private val shippingLabelOnboardingRepository: ShippingLabelOnboardingRepository,
private val shippingLabelRepository: WooShippingLabelRepository,
private val orderDetailsTransactionLauncher: OrderDetailsTransactionLauncher,
private val getOrderSubscriptions: GetOrderSubscriptions,
private val giftCardRepository: GiftCardRepository,
Expand Down Expand Up @@ -271,6 +273,7 @@ class OrderDetailViewModel @Inject constructor(
awaitAll(
fetchOrderAsync(),
fetchOrderNotesAsync(),
fetchShipmentsAsync(),
fetchOrderShippingLabelsAsync(),
fetchShipmentTrackingAsync(),
fetchOrderRefundsAsync(),
Expand Down Expand Up @@ -831,6 +834,13 @@ class OrderDetailViewModel @Inject constructor(
orderDetailsTransactionLauncher.onShipmentTrackingFetchingCompleted()
}

private fun fetchShipmentsAsync() = async {
if (shippingLabelOnboardingRepository.shippingPluginSupport.isSupported()) {
shippingLabelRepository.fetchConfig(selectedSite.get(), navArgs.orderId)
}
orderDetailsTransactionLauncher.onShipmentsFetchingCompleted()
}

private fun fetchOrderShippingLabelsAsync() = async {
if (shippingLabelOnboardingRepository.shippingPluginSupport.isSupported()) {
orderDetailRepository.fetchOrderShippingLabels(navArgs.orderId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class OrderDetailsTransactionLauncher @Inject constructor(

private enum class Conditions {
ORDER_FETCHED,
SHIPMENTS_FETCHED,
SHIPPING_LABEL_FETCHED,
NOTES_FETCHED,
REFUNDS_FETCHED,
Expand All @@ -58,6 +59,8 @@ class OrderDetailsTransactionLauncher @Inject constructor(

fun onOrderFetched() = satisfyCondition(Conditions.ORDER_FETCHED)

fun onShipmentsFetchingCompleted() = satisfyCondition(Conditions.SHIPMENTS_FETCHED)

fun onShippingLabelFetchingCompleted() = satisfyCondition(Conditions.SHIPPING_LABEL_FETCHED)

fun onNotesFetched() = satisfyCondition(Conditions.NOTES_FETCHED)
Expand Down Expand Up @@ -87,13 +90,15 @@ class OrderDetailsTransactionLauncher @Inject constructor(
performanceTransactionRepository.startTransaction(TRANSACTION_NAME, TransactionOperation.UI_LOAD)
waitingTimeTracker.start()
}

Lifecycle.Event.ON_DESTROY -> {
performanceTransactionId?.let {
performanceTransactionRepository.finishTransaction(it, TransactionStatus.ABORTED)
}
performanceTransactionId = null
waitingTimeTracker.abort()
}

else -> {
// no-op
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ package com.woocommerce.android.ui.orders.wooshippinglabels
import com.woocommerce.android.model.Order
import com.woocommerce.android.model.getNonRefundedProducts
import com.woocommerce.android.ui.orders.details.OrderDetailRepository
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.models.ShippableItemModel
import com.woocommerce.android.ui.products.details.ProductDetailRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class GetShippableItems @Inject constructor(
private val orderDetailRepository: OrderDetailRepository,
private val productDetailRepository: ProductDetailRepository
private val productDetailRepository: ProductDetailRepository,
private val configDataStore: WooShippingConfigDataStore,
) {
suspend operator fun invoke(order: Order): List<ShippableItemModel> {
val refunds = orderDetailRepository.getOrderRefunds(order.id)
val noRefundedProducts = refunds.getNonRefundedProducts(order.items)

val shipments = configDataStore.observeConfig(order.id).first()?.shipments // TODO Use this in the UI
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can test the output of this PR by debugging this shipment.


return noRefundedProducts.mapNotNull { item ->
productDetailRepository.getProductAsync(item.productId)?.let {
Pair(it, item)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.woocommerce.android.ui.orders.wooshippinglabels

import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigurationDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingStoreOptionsDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.models.StoreOptionsModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.transformLatest
import org.wordpress.android.fluxc.store.WooCommerceStore
import javax.inject.Inject

class ObserveStoreOptions @Inject constructor(
private val configurationDataStore: WooShippingConfigurationDataStore,
private val configurationDataStore: WooShippingStoreOptionsDataStore,
private val fetchAccountSettings: FetchAccountSettings,
private val wooStore: WooCommerceStore,
private val site: SelectedSite,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.woocommerce.android.ui.orders.wooshippinglabels.datasource

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import com.google.gson.Gson
import com.woocommerce.android.datastore.DataStoreQualifier
import com.woocommerce.android.datastore.DataStoreType
import com.woocommerce.android.tools.SelectedSite
import com.woocommerce.android.ui.orders.wooshippinglabels.networking.ConfigDTO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class WooShippingConfigDataStore @Inject constructor(
@DataStoreQualifier(DataStoreType.SHIPPING_LABEL_CONFIG) private val dataStore: DataStore<Preferences>,
private val gson: Gson,
private val selectedSite: SelectedSite
) {
private fun getConfigKey(orderId: Long) = "${selectedSite.getOrNull()?.siteId ?: ""}:${orderId}Config"

fun observeConfig(orderId: Long): Flow<ConfigDTO?> = dataStore.data.map { prefs ->
val config = prefs[stringPreferencesKey(getConfigKey(orderId))]
runCatching { gson.fromJson(config, ConfigDTO::class.java) }.getOrNull()
}

suspend fun saveConfig(orderId: Long, config: ConfigDTO) {
dataStore.edit { preferences ->
preferences[stringPreferencesKey(getConfigKey(orderId))] = gson.toJson(config)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class WooShippingConfigurationDataStore @Inject constructor(
@DataStoreQualifier(DataStoreType.SHIPPING_LABEL_CONFIGURATION) private val dataStore: DataStore<Preferences>,
class WooShippingStoreOptionsDataStore @Inject constructor(
@DataStoreQualifier(DataStoreType.SHIPPING_LABEL_STORE_OPTIONS) private val dataStore: DataStore<Preferences>,
private val gson: Gson,
private val selectedSite: SelectedSite
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package com.woocommerce.android.ui.orders.wooshippinglabels.networking

import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParser
import com.google.gson.annotations.JsonAdapter
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import com.woocommerce.android.ui.orders.wooshippinglabels.rates.networking.DestinationAddressDTO
import java.lang.reflect.Type
import java.math.BigDecimal

data class AccountSettingsDTO(
Expand All @@ -15,6 +23,24 @@ data class StoreOptionsDTO(
@SerializedName("origin_country") val originCountry: String? = null
)

data class ConfigResponse(
@SerializedName("success") val success: Boolean? = null,
@SerializedName("config") val config: ConfigDTO
)

/**
* Alias for a mapping of shipment id (as String) to the list of items contained in each shipment.
*/
typealias ShipmentMap = Map<String, List<Item>>

data class ConfigDTO(
@SerializedName("shipments")
@JsonAdapter(ShipmentMapDeserializer::class)
val shipments: ShipmentMap? = null
)

data class Item(@SerializedName("id") val id: Long?, @SerializedName("subItems") val subItems: List<String>?)

data class GetShippingLabelResponse(
@SerializedName("success") val success: Boolean? = null,
@SerializedName("labels") val shippingLabels: List<ShippingLabelDTO>? = null
Expand Down Expand Up @@ -133,3 +159,17 @@ data class CustomsItemDTO(
@SerializedName("origin_country") val originCountry: String,
@SerializedName("product_id") val productId: Long
)

private class ShipmentMapDeserializer : JsonDeserializer<ShipmentMap> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ShipmentMap {
// Handle string-encoded JSON or direct JSON object
val jsonObject = if (json.isJsonPrimitive && json.asJsonPrimitive.isString) {
JsonParser.parseString(json.asString).asJsonObject
} else {
json.asJsonObject
}

val mapType = object : TypeToken<ShipmentMap>() {}.type
return Gson().fromJson(jsonObject, mapType)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import com.woocommerce.android.model.Address
import com.woocommerce.android.ui.orders.shippinglabels.creation.ShippingLabelHazmatCategory
import com.woocommerce.android.ui.orders.wooshippinglabels.customs.CustomsData
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingAddressDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigurationDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingConfigDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.datasource.WooShippingStoreOptionsDataStore
import com.woocommerce.android.ui.orders.wooshippinglabels.models.AddressNormalizationModel
import com.woocommerce.android.ui.orders.wooshippinglabels.models.DestinationShippingAddress
import com.woocommerce.android.ui.orders.wooshippinglabels.models.OriginShippingAddress
Expand All @@ -22,7 +23,8 @@ import javax.inject.Inject
class WooShippingLabelRepository @Inject constructor(
private val restClient: WooShippingLabelRestClient,
private val mapper: WooShippingNetworkingMapper,
private val configurationDataStore: WooShippingConfigurationDataStore,
private val configDataStore: WooShippingConfigDataStore,
private val configurationDataStore: WooShippingStoreOptionsDataStore,
private val addressDataStore: WooShippingAddressDataStore
) {
suspend fun fetchShippingLabelPrinting(
Expand All @@ -48,6 +50,11 @@ class WooShippingLabelRepository @Inject constructor(
}
}

suspend fun fetchConfig(site: SiteModel, orderId: Long) = restClient.fetchConfig(site, orderId).asWooResult()
.also { response ->
response.model?.takeIf { !response.isError }?.let { configDataStore.saveConfig(orderId, it.config) }
}

suspend fun fetchPurchasedShippingLabels(
site: SiteModel,
orderId: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ class WooShippingLabelRestClient @Inject constructor(
return result.toWooPayload()
}

suspend fun fetchConfig(site: SiteModel, orderId: Long): WooPayload<ConfigResponse> {
val url = "/wcshipping/v1/config/label-purchase/$orderId"

val result = wooNetwork.executeGetGsonRequest(
site = site,
path = url,
clazz = ConfigResponse::class.java,
)

return result.toWooPayload()
}

suspend fun fetchPurchasedShippingLabels(
site: SiteModel,
orderId: Long,
Expand Down
Loading