Skip to content
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

[Products] Extract subscriptions outside of the Product DB table - Part 1 #12766

Merged
merged 16 commits into from
Oct 16, 2024
Merged
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
Expand Up @@ -83,7 +83,6 @@ data class Product(
override val width: Float,
override val height: Float,
override val weight: Float,
val subscription: SubscriptionDetails?,
val isSampleProduct: Boolean,
val specialStockStatus: ProductStockStatus? = null,
val isConfigurable: Boolean = false,
Expand Down Expand Up @@ -153,7 +152,6 @@ data class Product(
downloadExpiry == product.downloadExpiry &&
isDownloadable == product.isDownloadable &&
attributes == product.attributes &&
subscription == product.subscription &&
specialStockStatus == product.specialStockStatus &&
minAllowedQuantity == product.minAllowedQuantity &&
maxAllowedQuantity == product.maxAllowedQuantity &&
Expand All @@ -169,8 +167,7 @@ data class Product(
get() {
return weight > 0 ||
length > 0 || width > 0 || height > 0 ||
shippingClass.isNotEmpty() ||
subscription?.oneTimeShipping == true
shippingClass.isNotEmpty()
}
val productType get() = ProductType.fromString(type)
val variationEnabledAttributes
Expand Down Expand Up @@ -334,7 +331,6 @@ data class Product(
downloads = updatedProduct.downloads,
downloadLimit = updatedProduct.downloadLimit,
downloadExpiry = updatedProduct.downloadExpiry,
subscription = updatedProduct.subscription,
specialStockStatus = specialStockStatus,
minAllowedQuantity = updatedProduct.minAllowedQuantity,
maxAllowedQuantity = updatedProduct.maxAllowedQuantity,
Expand Down Expand Up @@ -482,20 +478,10 @@ fun Product.toDataModel(storedProductModel: WCProductModel? = null): WCProductMo
it.groupOfQuantity = groupOfQuantity ?: -1
it.combineVariationQuantities = combineVariationQuantities ?: false
it.password = password
// Subscription details are currently the only editable metadata fields from the app.
it.metadata = subscription?.toMetadataJson().toString()
}
}

fun WCProductModel.toAppModel(): Product {
val productType = ProductType.fromString(type)
val subscription = if (
productType == ProductType.SUBSCRIPTION || productType == ProductType.VARIABLE_SUBSCRIPTION
) {
SubscriptionDetailsMapper.toAppModel(this.metadata)
} else {
null
}
return Product(
remoteId = this.remoteProductId,
parentId = this.parentId,
Expand Down Expand Up @@ -580,7 +566,6 @@ fun WCProductModel.toAppModel(): Product {
upsellProductIds = this.getUpsellProductIdList(),
variationIds = this.getVariationIdList(),
isPurchasable = this.purchasable,
subscription = subscription,
isSampleProduct = isSampleProduct,
specialStockStatus = if (this.specialStockStatus.isNotNullOrEmpty()) {
ProductStockStatus.fromString(this.specialStockStatus)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.woocommerce.android.model

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

/**
* Container class for product and any additional details that are stored as product metadata.
*
* For now, the additional details include subscription details only.
*
* @param product The product.
* @param subscription The subscription details.
*/
@Parcelize
data class ProductAggregate(
val product: Product,
val subscription: SubscriptionDetails? = null
) : Parcelable {
val remoteId: Long
get() = product.remoteId

val hasShipping: Boolean
Copy link
Contributor

Choose a reason for hiding this comment

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

I've noticed a bug around this. But i could confirm is not related to the changes from this PR so I created a GH issue to address it.

get() = product.hasShipping || subscription?.oneTimeShipping == true

fun isSame(other: ProductAggregate): Boolean {
return product.isSameProduct(other.product) && subscription == other.subscription
}

fun merge(other: ProductAggregate): ProductAggregate {
return copy(
product = product.mergeProduct(other.product),
subscription = other.subscription
)
}
}
Original file line number Diff line number Diff line change
@@ -1,85 +1,81 @@
package com.woocommerce.android.model

import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.wordpress.android.fluxc.model.WCProductModel.SubscriptionMetadataKeys
import org.wordpress.android.fluxc.model.metadata.WCMetaData
import org.wordpress.android.fluxc.model.metadata.get

object SubscriptionDetailsMapper {
private val gson by lazy { Gson() }
fun toAppModel(metadata: String): SubscriptionDetails? {
val jsonArray = gson.fromJson(metadata, JsonArray::class.java) ?: return null
fun toAppModel(metadata: List<WCMetaData>): SubscriptionDetails? {
if (metadata.none { it.key in SubscriptionMetadataKeys.ALL_KEYS }) {
return null
}

val subscriptionInformation = jsonArray
.mapNotNull { it as? JsonObject }
.filter { jsonObject -> jsonObject[WCMetaData.KEY].asString in SubscriptionMetadataKeys.ALL_KEYS }
.associate { jsonObject ->
jsonObject[WCMetaData.KEY].asString to jsonObject[WCMetaData.VALUE]
}
val price = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_PRICE]?.valueAsString?.toBigDecimalOrNull()

return if (subscriptionInformation.isNotEmpty()) {
val price = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_PRICE]?.asString
?.toBigDecimalOrNull()

val periodString = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_PERIOD]?.asString ?: ""
val period = SubscriptionPeriod.fromValue(periodString)

val periodIntervalString = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_PERIOD_INTERVAL]
?.asString ?: ""
val periodInterval = periodIntervalString.toIntOrNull() ?: 0

val lengthInt = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_LENGTH]?.asString
?.toIntOrNull()
val length = if (lengthInt != null && lengthInt > 0) lengthInt else null

val signUpFee = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_SIGN_UP_FEE]?.asString
?.toBigDecimalOrNull()

val trialPeriodString = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_TRIAL_PERIOD]
?.asString
val trialPeriod = trialPeriodString?.let { SubscriptionPeriod.fromValue(trialPeriodString) }

val trialLengthInt = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_TRIAL_LENGTH]
?.asString?.toIntOrNull()
val trialLength = if (trialLengthInt != null && trialLengthInt > 0) trialLengthInt else null

val oneTimeShipping = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_ONE_TIME_SHIPPING]
?.asString == "yes"

val paymentsSyncDate = subscriptionInformation[SubscriptionMetadataKeys.SUBSCRIPTION_PAYMENT_SYNC_DATE]
?.extractPaymentsSyncDate()

SubscriptionDetails(
price = price,
period = period,
periodInterval = periodInterval,
length = length,
signUpFee = signUpFee,
trialPeriod = trialPeriod,
trialLength = trialLength,
oneTimeShipping = oneTimeShipping,
paymentsSyncDate = paymentsSyncDate
)
} else {
null
}
val periodString = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_PERIOD]?.valueAsString ?: ""
val period = SubscriptionPeriod.fromValue(periodString)

val periodIntervalString = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_PERIOD_INTERVAL]
?.valueAsString ?: ""
val periodInterval = periodIntervalString.toIntOrNull() ?: 0

val lengthInt = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_LENGTH]?.valueAsString
?.toIntOrNull()
val length = if (lengthInt != null && lengthInt > 0) lengthInt else null

val signUpFee = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_SIGN_UP_FEE]?.valueAsString
?.toBigDecimalOrNull()

val trialPeriodString = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_TRIAL_PERIOD]
?.valueAsString
val trialPeriod = trialPeriodString?.let { SubscriptionPeriod.fromValue(trialPeriodString) }

val trialLengthInt = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_TRIAL_LENGTH]
?.valueAsString?.toIntOrNull()
val trialLength = if (trialLengthInt != null && trialLengthInt > 0) trialLengthInt else null

val oneTimeShipping = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_ONE_TIME_SHIPPING]
?.valueAsString == "yes"

val paymentsSyncDate = metadata[SubscriptionMetadataKeys.SUBSCRIPTION_PAYMENT_SYNC_DATE]
?.extractPaymentsSyncDate()

return SubscriptionDetails(
price = price,
period = period,
periodInterval = periodInterval,
length = length,
signUpFee = signUpFee,
trialPeriod = trialPeriod,
trialLength = trialLength,
oneTimeShipping = oneTimeShipping,
paymentsSyncDate = paymentsSyncDate
)
}

fun toAppModel(metadata: String): SubscriptionDetails? {
val metadataList = gson.fromJson(metadata, Array<WCMetaData>::class.java)?.toList() ?: return null

return toAppModel(metadataList)
}

private fun JsonElement.extractPaymentsSyncDate(): SubscriptionPaymentSyncDate? {
return when {
isJsonObject -> asJsonObject.let {
val day = it["day"].asInt
val month = it["month"].asInt
private fun WCMetaData.extractPaymentsSyncDate(): SubscriptionPaymentSyncDate? {
return when (isJson) {
true -> value.stringValue.let {
val jsonObject = JsonParser.parseString(it).asJsonObject
val day = jsonObject["day"].asInt
val month = jsonObject["month"].asInt
if (day == 0) {
SubscriptionPaymentSyncDate.None
} else {
SubscriptionPaymentSyncDate.MonthDay(day, month)
SubscriptionPaymentSyncDate.MonthDay(month = month, day = day)
}
}

else -> asString?.toIntOrNull()?.let { day ->
false -> valueAsString.toIntOrNull()?.let { day ->
if (day == 0) {
SubscriptionPaymentSyncDate.None
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.woocommerce.android.ui.products

import com.woocommerce.android.model.Product
import com.woocommerce.android.model.ProductAggregate
import com.woocommerce.android.model.SubscriptionDetails
import com.woocommerce.android.model.SubscriptionPeriod
import com.woocommerce.android.ui.products.ProductBackorderStatus.NotAvailable
import com.woocommerce.android.ui.products.ProductStatus.DRAFT
import com.woocommerce.android.ui.products.ProductStatus.PUBLISH
import com.woocommerce.android.ui.products.ProductStockStatus.InStock
import com.woocommerce.android.ui.products.ProductType.SUBSCRIPTION
import com.woocommerce.android.ui.products.ProductType.VARIABLE_SUBSCRIPTION
import com.woocommerce.android.ui.products.settings.ProductCatalogVisibility.VISIBLE
import java.math.BigDecimal
import java.util.Date
Expand Down Expand Up @@ -93,12 +92,6 @@ object ProductHelper {
variationIds = listOf(),
downloads = listOf(),
isPurchasable = false,
subscription =
if (productType == SUBSCRIPTION || productType == VARIABLE_SUBSCRIPTION) {
getDefaultSubscriptionDetails()
} else {
null
},
isSampleProduct = false,
parentId = 0,
minAllowedQuantity = null,
Expand All @@ -111,6 +104,19 @@ object ProductHelper {
)
}

fun getDefaultProductAggregate(productType: ProductType, isVirtual: Boolean): ProductAggregate {
return ProductAggregate(
product = getDefaultNewProduct(productType, isVirtual),
subscription = if (productType == ProductType.SUBSCRIPTION ||
productType == ProductType.VARIABLE_SUBSCRIPTION
) {
getDefaultSubscriptionDetails()
} else {
null
}
)
}

fun getDefaultSubscriptionDetails(): SubscriptionDetails =
SubscriptionDetails(
price = null,
Expand Down
Loading
Loading