From 9f715a424a6dac967455ed35c9bd1bfd873f2fa1 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 16:55:51 +0200 Subject: [PATCH 01/19] Migrate `WCProductVariationModel` to Room `:libs:fluxc-plugin:assembleDebug` passes --- .../40.json | 2556 +++++++++++++++++ .../fluxc/model/WCProductVariationModel.kt | 159 +- .../wpcom/wc/product/ProductRestClient.kt | 75 +- .../wc/product/ProductVariationApiResponse.kt | 118 +- .../wc/product/ProductVariationMapper.kt | 6 +- .../fluxc/persistence/ProductSqlUtils.kt | 101 +- .../fluxc/persistence/WCAndroidDatabase.kt | 7 +- .../android/fluxc/store/WCProductStore.kt | 18 +- 8 files changed, 2777 insertions(+), 263 deletions(-) create mode 100644 libs/fluxc-plugin/schemas/org.wordpress.android.fluxc.persistence.WCAndroidDatabase/40.json diff --git a/libs/fluxc-plugin/schemas/org.wordpress.android.fluxc.persistence.WCAndroidDatabase/40.json b/libs/fluxc-plugin/schemas/org.wordpress.android.fluxc.persistence.WCAndroidDatabase/40.json new file mode 100644 index 00000000000..1bab84e0426 --- /dev/null +++ b/libs/fluxc-plugin/schemas/org.wordpress.android.fluxc.persistence.WCAndroidDatabase/40.json @@ -0,0 +1,2556 @@ +{ + "formatVersion": 1, + "database": { + "version": 40, + "identityHash": "de6bdbb11d4170a5f39d18b71bea6de7", + "entities": [ + { + "tableName": "AddonEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`addonLocalId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `globalGroupLocalId` INTEGER, `productRemoteId` INTEGER, `localSiteId` INTEGER, `type` TEXT NOT NULL, `display` TEXT, `name` TEXT NOT NULL, `titleFormat` TEXT NOT NULL, `description` TEXT, `required` INTEGER NOT NULL, `position` INTEGER NOT NULL, `restrictions` TEXT, `priceType` TEXT, `price` TEXT, `min` INTEGER, `max` INTEGER, FOREIGN KEY(`globalGroupLocalId`) REFERENCES `GlobalAddonGroupEntity`(`globalGroupLocalId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "addonLocalId", + "columnName": "addonLocalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "globalGroupLocalId", + "columnName": "globalGroupLocalId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "productRemoteId", + "columnName": "productRemoteId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "display", + "columnName": "display", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "titleFormat", + "columnName": "titleFormat", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "required", + "columnName": "required", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "restrictions", + "columnName": "restrictions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "priceType", + "columnName": "priceType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "min", + "columnName": "min", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "max", + "columnName": "max", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "addonLocalId" + ] + }, + "indices": [ + { + "name": "index_AddonEntity_globalGroupLocalId", + "unique": false, + "columnNames": [ + "globalGroupLocalId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_AddonEntity_globalGroupLocalId` ON `${TABLE_NAME}` (`globalGroupLocalId`)" + } + ], + "foreignKeys": [ + { + "table": "GlobalAddonGroupEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "globalGroupLocalId" + ], + "referencedColumns": [ + "globalGroupLocalId" + ] + } + ] + }, + { + "tableName": "AddonOptionEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`addonOptionLocalId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `addonLocalId` INTEGER NOT NULL, `priceType` TEXT NOT NULL, `label` TEXT, `price` TEXT, `image` TEXT, FOREIGN KEY(`addonLocalId`) REFERENCES `AddonEntity`(`addonLocalId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "addonOptionLocalId", + "columnName": "addonOptionLocalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "addonLocalId", + "columnName": "addonLocalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "priceType", + "columnName": "priceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "addonOptionLocalId" + ] + }, + "indices": [ + { + "name": "index_AddonOptionEntity_addonLocalId", + "unique": false, + "columnNames": [ + "addonLocalId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_AddonOptionEntity_addonLocalId` ON `${TABLE_NAME}` (`addonLocalId`)" + } + ], + "foreignKeys": [ + { + "table": "AddonEntity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "addonLocalId" + ], + "referencedColumns": [ + "addonLocalId" + ] + } + ] + }, + { + "tableName": "Coupons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localSiteId` INTEGER NOT NULL, `code` TEXT, `amount` TEXT, `dateCreated` TEXT, `dateCreatedGmt` TEXT, `dateModified` TEXT, `dateModifiedGmt` TEXT, `discountType` TEXT, `description` TEXT, `dateExpires` TEXT, `dateExpiresGmt` TEXT, `usageCount` INTEGER, `isForIndividualUse` INTEGER, `usageLimit` INTEGER, `usageLimitPerUser` INTEGER, `limitUsageToXItems` INTEGER, `isShippingFree` INTEGER, `areSaleItemsExcluded` INTEGER, `minimumAmount` TEXT, `maximumAmount` TEXT, `includedProductIds` TEXT, `excludedProductIds` TEXT, `includedCategoryIds` TEXT, `excludedCategoryIds` TEXT, PRIMARY KEY(`id`, `localSiteId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateCreatedGmt", + "columnName": "dateCreatedGmt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateModified", + "columnName": "dateModified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateModifiedGmt", + "columnName": "dateModifiedGmt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "discountType", + "columnName": "discountType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateExpires", + "columnName": "dateExpires", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateExpiresGmt", + "columnName": "dateExpiresGmt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isForIndividualUse", + "columnName": "isForIndividualUse", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "usageLimit", + "columnName": "usageLimit", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "usageLimitPerUser", + "columnName": "usageLimitPerUser", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "limitUsageToXItems", + "columnName": "limitUsageToXItems", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isShippingFree", + "columnName": "isShippingFree", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "areSaleItemsExcluded", + "columnName": "areSaleItemsExcluded", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "minimumAmount", + "columnName": "minimumAmount", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "maximumAmount", + "columnName": "maximumAmount", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "includedProductIds", + "columnName": "includedProductIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "excludedProductIds", + "columnName": "excludedProductIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "includedCategoryIds", + "columnName": "includedCategoryIds", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "excludedCategoryIds", + "columnName": "excludedCategoryIds", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "localSiteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CouponEmails", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`couponId` INTEGER NOT NULL, `localSiteId` INTEGER NOT NULL, `email` TEXT NOT NULL, PRIMARY KEY(`couponId`, `localSiteId`, `email`), FOREIGN KEY(`couponId`, `localSiteId`) REFERENCES `Coupons`(`id`, `localSiteId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "couponId", + "columnName": "couponId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "couponId", + "localSiteId", + "email" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "Coupons", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "couponId", + "localSiteId" + ], + "referencedColumns": [ + "id", + "localSiteId" + ] + } + ] + }, + { + "tableName": "GlobalAddonGroupEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`globalGroupLocalId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `restrictedCategoriesIds` TEXT NOT NULL, `localSiteId` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "globalGroupLocalId", + "columnName": "globalGroupLocalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "restrictedCategoriesIds", + "columnName": "restrictedCategoriesIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "globalGroupLocalId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OrderNotes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `noteId` INTEGER NOT NULL, `orderId` INTEGER NOT NULL, `dateCreated` TEXT, `note` TEXT, `author` TEXT, `isSystemNote` INTEGER NOT NULL, `isCustomerNote` INTEGER NOT NULL, PRIMARY KEY(`localSiteId`, `noteId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "noteId", + "columnName": "noteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "orderId", + "columnName": "orderId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isSystemNote", + "columnName": "isSystemNote", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCustomerNote", + "columnName": "isCustomerNote", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "noteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OrderEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `orderId` INTEGER NOT NULL, `number` TEXT NOT NULL, `status` TEXT NOT NULL, `currency` TEXT NOT NULL, `orderKey` TEXT NOT NULL, `dateCreated` TEXT NOT NULL, `dateModified` TEXT NOT NULL, `total` TEXT NOT NULL, `totalTax` TEXT NOT NULL, `shippingTotal` TEXT NOT NULL, `paymentMethod` TEXT NOT NULL, `paymentMethodTitle` TEXT NOT NULL, `datePaid` TEXT NOT NULL, `pricesIncludeTax` INTEGER NOT NULL, `customerNote` TEXT NOT NULL, `discountTotal` TEXT NOT NULL, `discountCodes` TEXT NOT NULL, `refundTotal` TEXT NOT NULL, `customerId` INTEGER NOT NULL DEFAULT 0, `billingFirstName` TEXT NOT NULL, `billingLastName` TEXT NOT NULL, `billingCompany` TEXT NOT NULL, `billingAddress1` TEXT NOT NULL, `billingAddress2` TEXT NOT NULL, `billingCity` TEXT NOT NULL, `billingState` TEXT NOT NULL, `billingPostcode` TEXT NOT NULL, `billingCountry` TEXT NOT NULL, `billingEmail` TEXT NOT NULL, `billingPhone` TEXT NOT NULL, `shippingFirstName` TEXT NOT NULL, `shippingLastName` TEXT NOT NULL, `shippingCompany` TEXT NOT NULL, `shippingAddress1` TEXT NOT NULL, `shippingAddress2` TEXT NOT NULL, `shippingCity` TEXT NOT NULL, `shippingState` TEXT NOT NULL, `shippingPostcode` TEXT NOT NULL, `shippingCountry` TEXT NOT NULL, `shippingPhone` TEXT NOT NULL, `lineItems` TEXT NOT NULL, `shippingLines` TEXT NOT NULL, `feeLines` TEXT NOT NULL, `taxLines` TEXT NOT NULL, `couponLines` TEXT NOT NULL DEFAULT '', `metaData` TEXT NOT NULL, `paymentUrl` TEXT NOT NULL DEFAULT '', `isEditable` INTEGER NOT NULL DEFAULT 1, `needsPayment` INTEGER, `needsProcessing` INTEGER, `giftCardCode` TEXT NOT NULL DEFAULT '', `giftCardAmount` TEXT NOT NULL DEFAULT '', `shippingTax` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`localSiteId`, `orderId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "orderId", + "columnName": "orderId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderKey", + "columnName": "orderKey", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateModified", + "columnName": "dateModified", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "total", + "columnName": "total", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalTax", + "columnName": "totalTax", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingTotal", + "columnName": "shippingTotal", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "paymentMethod", + "columnName": "paymentMethod", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "paymentMethodTitle", + "columnName": "paymentMethodTitle", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "datePaid", + "columnName": "datePaid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pricesIncludeTax", + "columnName": "pricesIncludeTax", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "customerNote", + "columnName": "customerNote", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "discountTotal", + "columnName": "discountTotal", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "discountCodes", + "columnName": "discountCodes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "refundTotal", + "columnName": "refundTotal", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "customerId", + "columnName": "customerId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "billingFirstName", + "columnName": "billingFirstName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingLastName", + "columnName": "billingLastName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingCompany", + "columnName": "billingCompany", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingAddress1", + "columnName": "billingAddress1", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingAddress2", + "columnName": "billingAddress2", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingCity", + "columnName": "billingCity", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingState", + "columnName": "billingState", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingPostcode", + "columnName": "billingPostcode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingCountry", + "columnName": "billingCountry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingEmail", + "columnName": "billingEmail", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "billingPhone", + "columnName": "billingPhone", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingFirstName", + "columnName": "shippingFirstName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingLastName", + "columnName": "shippingLastName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingCompany", + "columnName": "shippingCompany", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingAddress1", + "columnName": "shippingAddress1", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingAddress2", + "columnName": "shippingAddress2", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingCity", + "columnName": "shippingCity", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingState", + "columnName": "shippingState", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingPostcode", + "columnName": "shippingPostcode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingCountry", + "columnName": "shippingCountry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingPhone", + "columnName": "shippingPhone", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lineItems", + "columnName": "lineItems", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingLines", + "columnName": "shippingLines", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "feeLines", + "columnName": "feeLines", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "taxLines", + "columnName": "taxLines", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "couponLines", + "columnName": "couponLines", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "metaData", + "columnName": "metaData", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "paymentUrl", + "columnName": "paymentUrl", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "isEditable", + "columnName": "isEditable", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "needsPayment", + "columnName": "needsPayment", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "needsProcessing", + "columnName": "needsProcessing", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "giftCardCode", + "columnName": "giftCardCode", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "giftCardAmount", + "columnName": "giftCardAmount", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + }, + { + "fieldPath": "shippingTax", + "columnName": "shippingTax", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "''" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "orderId" + ] + }, + "indices": [ + { + "name": "index_OrderEntity_localSiteId_orderId", + "unique": false, + "columnNames": [ + "localSiteId", + "orderId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_OrderEntity_localSiteId_orderId` ON `${TABLE_NAME}` (`localSiteId`, `orderId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "MetaData", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `parentItemId` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT NOT NULL, `type` TEXT NOT NULL DEFAULT 'ORDER', PRIMARY KEY(`localSiteId`, `parentItemId`, `id`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "parentItemId", + "columnName": "parentItemId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'ORDER'" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "parentItemId", + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "InboxNotes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `remoteId` INTEGER NOT NULL, `localSiteId` INTEGER NOT NULL, `name` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `dateCreated` TEXT NOT NULL, `status` TEXT NOT NULL, `source` TEXT, `type` TEXT, `dateReminder` TEXT)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "localId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteId", + "columnName": "remoteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateReminder", + "columnName": "dateReminder", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "localId" + ] + }, + "indices": [ + { + "name": "index_InboxNotes_remoteId_localSiteId", + "unique": true, + "columnNames": [ + "remoteId", + "localSiteId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_InboxNotes_remoteId_localSiteId` ON `${TABLE_NAME}` (`remoteId`, `localSiteId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "InboxNoteActions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remoteId` INTEGER NOT NULL, `inboxNoteLocalId` INTEGER NOT NULL, `localSiteId` INTEGER NOT NULL, `name` TEXT NOT NULL, `label` TEXT NOT NULL, `url` TEXT NOT NULL, `query` TEXT, `status` TEXT, `primary` INTEGER NOT NULL, `actionedText` TEXT, PRIMARY KEY(`remoteId`, `inboxNoteLocalId`), FOREIGN KEY(`inboxNoteLocalId`) REFERENCES `InboxNotes`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remoteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inboxNoteLocalId", + "columnName": "inboxNoteLocalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "label", + "columnName": "label", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "actionedText", + "columnName": "actionedText", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "remoteId", + "inboxNoteLocalId" + ] + }, + "indices": [ + { + "name": "index_InboxNoteActions_inboxNoteLocalId", + "unique": false, + "columnNames": [ + "inboxNoteLocalId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_InboxNoteActions_inboxNoteLocalId` ON `${TABLE_NAME}` (`inboxNoteLocalId`)" + } + ], + "foreignKeys": [ + { + "table": "InboxNotes", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "inboxNoteLocalId" + ], + "referencedColumns": [ + "localId" + ] + } + ] + }, + { + "tableName": "TopPerformerProducts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `datePeriod` TEXT NOT NULL, `productId` INTEGER NOT NULL, `name` TEXT NOT NULL, `imageUrl` TEXT, `quantity` INTEGER NOT NULL, `currency` TEXT NOT NULL, `total` REAL NOT NULL, `millisSinceLastUpdated` INTEGER NOT NULL, PRIMARY KEY(`datePeriod`, `productId`, `localSiteId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "datePeriod", + "columnName": "datePeriod", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "productId", + "columnName": "productId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "imageUrl", + "columnName": "imageUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quantity", + "columnName": "quantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "total", + "columnName": "total", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "millisSinceLastUpdated", + "columnName": "millisSinceLastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "datePeriod", + "productId", + "localSiteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TaxBasedOnSetting", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `selectedOption` TEXT NOT NULL, PRIMARY KEY(`localSiteId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "selectedOption", + "columnName": "selectedOption", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "TaxRate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localSiteId` INTEGER NOT NULL, `country` TEXT, `state` TEXT, `postcode` TEXT, `city` TEXT, `rate` TEXT, `name` TEXT, `taxClass` TEXT, PRIMARY KEY(`id`, `localSiteId`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "country", + "columnName": "country", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "postcode", + "columnName": "postcode", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "city", + "columnName": "city", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "taxClass", + "columnName": "taxClass", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "localSiteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "WooPaymentsDepositsOverview", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `depositsEnabled` INTEGER, `depositsBlocked` INTEGER, `defaultCurrency` TEXT, `delayDays` INTEGER, `weeklyAnchor` TEXT, `monthlyAnchor` INTEGER, `interval` TEXT, PRIMARY KEY(`localSiteId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "account.depositsEnabled", + "columnName": "depositsEnabled", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account.depositsBlocked", + "columnName": "depositsBlocked", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account.defaultCurrency", + "columnName": "defaultCurrency", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "account.depositsSchedule.delayDays", + "columnName": "delayDays", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account.depositsSchedule.weeklyAnchor", + "columnName": "weeklyAnchor", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "account.depositsSchedule.monthlyAnchor", + "columnName": "monthlyAnchor", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account.depositsSchedule.interval", + "columnName": "interval", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "WooPaymentsDeposits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `localSiteId` INTEGER NOT NULL, `depositId` TEXT, `date` INTEGER, `type` TEXT, `amount` INTEGER, `status` TEXT, `bankAccount` TEXT, `currency` TEXT, `automatic` INTEGER, `fee` INTEGER, `feePercentage` REAL, `created` INTEGER, `depositType` TEXT NOT NULL, FOREIGN KEY(`localSiteId`) REFERENCES `WooPaymentsDepositsOverview`(`localSiteId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "depositId", + "columnName": "depositId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bankAccount", + "columnName": "bankAccount", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "automatic", + "columnName": "automatic", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "feePercentage", + "columnName": "feePercentage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "depositType", + "columnName": "depositType", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "WooPaymentsDepositsOverview", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "localSiteId" + ], + "referencedColumns": [ + "localSiteId" + ] + } + ] + }, + { + "tableName": "WooPaymentsManualDeposits", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `localSiteId` INTEGER NOT NULL, `currency` TEXT, `date` INTEGER, FOREIGN KEY(`localSiteId`) REFERENCES `WooPaymentsDepositsOverview`(`localSiteId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "WooPaymentsDepositsOverview", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "localSiteId" + ], + "referencedColumns": [ + "localSiteId" + ] + } + ] + }, + { + "tableName": "WooPaymentsBalance", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `localSiteId` INTEGER NOT NULL, `amount` INTEGER, `currency` TEXT, `fee` INTEGER, `feePercentage` REAL, `net` INTEGER, `balanceType` TEXT NOT NULL, `card` INTEGER, FOREIGN KEY(`localSiteId`) REFERENCES `WooPaymentsDepositsOverview`(`localSiteId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "currency", + "columnName": "currency", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "fee", + "columnName": "fee", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "feePercentage", + "columnName": "feePercentage", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "net", + "columnName": "net", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "balanceType", + "columnName": "balanceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourceTypes.card", + "columnName": "card", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "WooPaymentsDepositsOverview", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "localSiteId" + ], + "referencedColumns": [ + "localSiteId" + ] + } + ] + }, + { + "tableName": "VisitorSummaryStatsEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `date` TEXT NOT NULL, `granularity` TEXT NOT NULL, `views` INTEGER NOT NULL, `visitors` INTEGER NOT NULL, PRIMARY KEY(`localSiteId`, `date`, `granularity`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "granularity", + "columnName": "granularity", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "views", + "columnName": "views", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "visitors", + "columnName": "visitors", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "date", + "granularity" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ShippingMethod", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `localSiteId` INTEGER NOT NULL, `title` TEXT NOT NULL, PRIMARY KEY(`localSiteId`, `id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CustomerFromAnalytics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `id` INTEGER NOT NULL, `userId` INTEGER NOT NULL, `avgOrderValue` REAL NOT NULL, `city` TEXT NOT NULL, `country` TEXT NOT NULL, `dateLastActive` TEXT NOT NULL, `dateLastActiveGmt` TEXT NOT NULL, `dateLastOrder` TEXT NOT NULL, `dateRegistered` TEXT NOT NULL, `dateRegisteredGmt` TEXT NOT NULL, `email` TEXT NOT NULL, `name` TEXT NOT NULL, `ordersCount` INTEGER NOT NULL, `postcode` TEXT NOT NULL, `state` TEXT NOT NULL, `totalSpend` REAL NOT NULL, `username` TEXT NOT NULL, PRIMARY KEY(`localSiteId`, `id`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "avgOrderValue", + "columnName": "avgOrderValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "city", + "columnName": "city", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "country", + "columnName": "country", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateLastActive", + "columnName": "dateLastActive", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateLastActiveGmt", + "columnName": "dateLastActiveGmt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateLastOrder", + "columnName": "dateLastOrder", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateRegistered", + "columnName": "dateRegistered", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateRegisteredGmt", + "columnName": "dateRegisteredGmt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ordersCount", + "columnName": "ordersCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "postcode", + "columnName": "postcode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalSpend", + "columnName": "totalSpend", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ProductEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `remoteId` INTEGER NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `permalink` TEXT NOT NULL, `dateCreated` TEXT NOT NULL, `dateModified` TEXT NOT NULL, `type` TEXT NOT NULL, `status` TEXT NOT NULL, `featured` INTEGER NOT NULL, `catalogVisibility` TEXT NOT NULL, `description` TEXT NOT NULL, `shortDescription` TEXT NOT NULL, `sku` TEXT NOT NULL, `globalUniqueId` TEXT NOT NULL, `price` TEXT NOT NULL, `regularPrice` TEXT NOT NULL, `salePrice` TEXT NOT NULL, `onSale` INTEGER NOT NULL, `totalSales` INTEGER NOT NULL, `purchasable` INTEGER NOT NULL, `dateOnSaleFrom` TEXT NOT NULL, `dateOnSaleTo` TEXT NOT NULL, `dateOnSaleFromGmt` TEXT NOT NULL, `dateOnSaleToGmt` TEXT NOT NULL, `virtual` INTEGER NOT NULL, `downloadable` INTEGER NOT NULL, `downloadLimit` INTEGER NOT NULL, `downloadExpiry` INTEGER NOT NULL, `soldIndividually` INTEGER NOT NULL, `externalUrl` TEXT NOT NULL, `buttonText` TEXT NOT NULL, `taxStatus` TEXT NOT NULL, `taxClass` TEXT NOT NULL, `manageStock` INTEGER NOT NULL, `stockQuantity` REAL NOT NULL, `stockStatus` TEXT NOT NULL, `backorders` TEXT NOT NULL, `backordersAllowed` INTEGER NOT NULL, `backordered` INTEGER NOT NULL, `shippingRequired` INTEGER NOT NULL, `shippingTaxable` INTEGER NOT NULL, `shippingClass` TEXT NOT NULL, `shippingClassId` INTEGER NOT NULL, `reviewsAllowed` INTEGER NOT NULL, `averageRating` TEXT NOT NULL, `ratingCount` INTEGER NOT NULL, `parentId` INTEGER NOT NULL, `purchaseNote` TEXT NOT NULL, `menuOrder` INTEGER NOT NULL, `categories` TEXT NOT NULL, `tags` TEXT NOT NULL, `images` TEXT NOT NULL, `attributes` TEXT NOT NULL, `variations` TEXT NOT NULL, `downloads` TEXT NOT NULL, `relatedIds` TEXT NOT NULL, `crossSellIds` TEXT NOT NULL, `upsellIds` TEXT NOT NULL, `groupedProductIds` TEXT NOT NULL, `weight` TEXT NOT NULL, `length` TEXT NOT NULL, `width` TEXT NOT NULL, `height` TEXT NOT NULL, `bundledItems` TEXT NOT NULL, `compositeComponents` TEXT NOT NULL, `specialStockStatus` TEXT NOT NULL, `bundleMinSize` REAL, `bundleMaxSize` REAL, `minAllowedQuantity` INTEGER NOT NULL, `maxAllowedQuantity` INTEGER NOT NULL, `groupOfQuantity` INTEGER NOT NULL, `combineVariationQuantities` INTEGER NOT NULL, `password` TEXT, `isSampleProduct` INTEGER NOT NULL, PRIMARY KEY(`localSiteId`, `remoteId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteId", + "columnName": "remoteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "permalink", + "columnName": "permalink", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateModified", + "columnName": "dateModified", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "featured", + "columnName": "featured", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "catalogVisibility", + "columnName": "catalogVisibility", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shortDescription", + "columnName": "shortDescription", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sku", + "columnName": "sku", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "globalUniqueId", + "columnName": "globalUniqueId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "regularPrice", + "columnName": "regularPrice", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "salePrice", + "columnName": "salePrice", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "onSale", + "columnName": "onSale", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalSales", + "columnName": "totalSales", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "purchasable", + "columnName": "purchasable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateOnSaleFrom", + "columnName": "dateOnSaleFrom", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleTo", + "columnName": "dateOnSaleTo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleFromGmt", + "columnName": "dateOnSaleFromGmt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleToGmt", + "columnName": "dateOnSaleToGmt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "virtual", + "columnName": "virtual", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadable", + "columnName": "downloadable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadLimit", + "columnName": "downloadLimit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadExpiry", + "columnName": "downloadExpiry", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "soldIndividually", + "columnName": "soldIndividually", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "externalUrl", + "columnName": "externalUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "buttonText", + "columnName": "buttonText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "taxStatus", + "columnName": "taxStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "taxClass", + "columnName": "taxClass", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "manageStock", + "columnName": "manageStock", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "stockQuantity", + "columnName": "stockQuantity", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "stockStatus", + "columnName": "stockStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backorders", + "columnName": "backorders", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backordersAllowed", + "columnName": "backordersAllowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "backordered", + "columnName": "backordered", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shippingRequired", + "columnName": "shippingRequired", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shippingTaxable", + "columnName": "shippingTaxable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shippingClass", + "columnName": "shippingClass", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingClassId", + "columnName": "shippingClassId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reviewsAllowed", + "columnName": "reviewsAllowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "averageRating", + "columnName": "averageRating", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratingCount", + "columnName": "ratingCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "parentId", + "columnName": "parentId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "purchaseNote", + "columnName": "purchaseNote", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "menuOrder", + "columnName": "menuOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categories", + "columnName": "categories", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "images", + "columnName": "images", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "variations", + "columnName": "variations", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "downloads", + "columnName": "downloads", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "relatedIds", + "columnName": "relatedIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "crossSellIds", + "columnName": "crossSellIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "upsellIds", + "columnName": "upsellIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "groupedProductIds", + "columnName": "groupedProductIds", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "length", + "columnName": "length", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bundledItems", + "columnName": "bundledItems", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "compositeComponents", + "columnName": "compositeComponents", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "specialStockStatus", + "columnName": "specialStockStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bundleMinSize", + "columnName": "bundleMinSize", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "bundleMaxSize", + "columnName": "bundleMaxSize", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "minAllowedQuantity", + "columnName": "minAllowedQuantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "maxAllowedQuantity", + "columnName": "maxAllowedQuantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "groupOfQuantity", + "columnName": "groupOfQuantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "combineVariationQuantities", + "columnName": "combineVariationQuantities", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isSampleProduct", + "columnName": "isSampleProduct", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "remoteId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ProductVariationEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`localSiteId` INTEGER NOT NULL, `remoteProductId` INTEGER NOT NULL, `remoteVariationId` INTEGER NOT NULL, `dateCreated` TEXT NOT NULL, `dateModified` TEXT NOT NULL, `description` TEXT NOT NULL, `permalink` TEXT NOT NULL, `sku` TEXT NOT NULL, `globalUniqueId` TEXT NOT NULL, `status` TEXT NOT NULL, `price` TEXT NOT NULL, `regularPrice` TEXT NOT NULL, `salePrice` TEXT NOT NULL, `dateOnSaleFrom` TEXT NOT NULL, `dateOnSaleTo` TEXT NOT NULL, `dateOnSaleFromGmt` TEXT NOT NULL, `dateOnSaleToGmt` TEXT NOT NULL, `taxStatus` TEXT NOT NULL, `taxClass` TEXT NOT NULL, `onSale` INTEGER NOT NULL, `purchasable` INTEGER NOT NULL, `virtual` INTEGER NOT NULL, `downloadable` INTEGER NOT NULL, `downloadLimit` INTEGER NOT NULL, `downloadExpiry` INTEGER NOT NULL, `downloads` TEXT NOT NULL, `backorders` TEXT NOT NULL, `backordersAllowed` INTEGER NOT NULL, `backordered` INTEGER NOT NULL, `shippingClass` TEXT NOT NULL, `shippingClassId` INTEGER NOT NULL, `manageStock` INTEGER NOT NULL, `stockQuantity` REAL NOT NULL, `stockStatus` TEXT NOT NULL, `image` TEXT NOT NULL, `weight` TEXT NOT NULL, `length` TEXT NOT NULL, `width` TEXT NOT NULL, `height` TEXT NOT NULL, `minAllowedQuantity` INTEGER NOT NULL, `maxAllowedQuantity` INTEGER NOT NULL, `groupOfQuantity` INTEGER NOT NULL, `overrideProductQuantities` INTEGER NOT NULL, `menuOrder` INTEGER NOT NULL, `attributes` TEXT NOT NULL, `metadata` TEXT, PRIMARY KEY(`localSiteId`, `remoteProductId`, `remoteVariationId`))", + "fields": [ + { + "fieldPath": "localSiteId", + "columnName": "localSiteId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteProductId", + "columnName": "remoteProductId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteVariationId", + "columnName": "remoteVariationId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateModified", + "columnName": "dateModified", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "permalink", + "columnName": "permalink", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sku", + "columnName": "sku", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "globalUniqueId", + "columnName": "globalUniqueId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "regularPrice", + "columnName": "regularPrice", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "salePrice", + "columnName": "salePrice", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleFrom", + "columnName": "dateOnSaleFrom", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleTo", + "columnName": "dateOnSaleTo", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleFromGmt", + "columnName": "dateOnSaleFromGmt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateOnSaleToGmt", + "columnName": "dateOnSaleToGmt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "taxStatus", + "columnName": "taxStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "taxClass", + "columnName": "taxClass", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "onSale", + "columnName": "onSale", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "purchasable", + "columnName": "purchasable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "virtual", + "columnName": "virtual", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadable", + "columnName": "downloadable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadLimit", + "columnName": "downloadLimit", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloadExpiry", + "columnName": "downloadExpiry", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "downloads", + "columnName": "downloads", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backorders", + "columnName": "backorders", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "backordersAllowed", + "columnName": "backordersAllowed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "backordered", + "columnName": "backordered", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shippingClass", + "columnName": "shippingClass", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "shippingClassId", + "columnName": "shippingClassId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "manageStock", + "columnName": "manageStock", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "stockQuantity", + "columnName": "stockQuantity", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "stockStatus", + "columnName": "stockStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "weight", + "columnName": "weight", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "length", + "columnName": "length", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "width", + "columnName": "width", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "height", + "columnName": "height", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "minAllowedQuantity", + "columnName": "minAllowedQuantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "maxAllowedQuantity", + "columnName": "maxAllowedQuantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "groupOfQuantity", + "columnName": "groupOfQuantity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "overrideProductQuantities", + "columnName": "overrideProductQuantities", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "menuOrder", + "columnName": "menuOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attributes", + "columnName": "attributes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "metadata", + "columnName": "metadata", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "localSiteId", + "remoteProductId", + "remoteVariationId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'de6bdbb11d4170a5f39d18b71bea6de7')" + ] + } +} \ No newline at end of file diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt index 916ca797a39..1e60094e28b 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt @@ -1,17 +1,12 @@ package org.wordpress.android.fluxc.model +import androidx.room.Entity import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.JsonParseException -import com.google.gson.reflect.TypeToken -import com.yarolegovich.wellsql.core.Identifiable -import com.yarolegovich.wellsql.core.annotation.Column -import com.yarolegovich.wellsql.core.annotation.PrimaryKey -import com.yarolegovich.wellsql.core.annotation.Table import org.wordpress.android.fluxc.model.WCProductVariationModel.ProductVariantOption import org.wordpress.android.fluxc.network.utils.getLong import org.wordpress.android.fluxc.network.utils.getString -import org.wordpress.android.fluxc.persistence.WellSqlConfig import org.wordpress.android.util.AppLog import org.wordpress.android.util.AppLog.T import java.lang.IllegalStateException @@ -22,77 +17,58 @@ typealias VariationAttributes = List * Product variations - see http://woocommerce.github.io/woocommerce-rest-api-docs/#product-variations * As with WCProductModel, the backend returns more properties than are supported below */ -@Table(addOn = WellSqlConfig.ADDON_WOOCOMMERCE) -data class WCProductVariationModel(@PrimaryKey @Column private var id: Int = 0) : Identifiable { - @Column var localSiteId = 0 - @Column var remoteProductId = 0L - @Column var remoteVariationId = 0L - - @Column var dateCreated = "" - @Column var dateModified = "" - - @Column var description = "" - @Column var permalink = "" - @Column var sku = "" - @Column var globalUniqueId = "" - @Column var status = "" - - @Column var price = "" - @Column var regularPrice = "" - @Column var salePrice = "" - - @Column var dateOnSaleFrom = "" - @Column var dateOnSaleTo = "" - @Column var dateOnSaleFromGmt = "" - @Column var dateOnSaleToGmt = "" - - @Column var taxStatus = "" // taxable, shipping, none - @Column var taxClass = "" - - @Column var onSale = false - @Column var purchasable = false - @Column var virtual = false - @Column var downloadable = false - - @Column var downloadLimit = 0L - @Column var downloadExpiry = 0 - - @Column var downloads = "" // array of downloadable files - @Column var backorders = "" // no, notify, yes - - @Column var backordersAllowed = false - @Column var backordered = false - - @Column var shippingClass = "" - @Column var shippingClassId = 0 - - @Column var manageStock = false - @Column var stockQuantity = 0.0 - @Column var stockStatus = "" - - @Column var image = "" - - @Column var weight = "" - @Column var length = "" - @Column var width = "" - @Column var height = "" - - @Column var minAllowedQuantity = -1 - @Column var maxAllowedQuantity = -1 - @Column var groupOfQuantity = -1 - @Column var overrideProductQuantities = false - - @Column var menuOrder = 0 - - @Column var attributes = "" - - @Column var metadata: String? = null - - override fun getId() = id - - override fun setId(id: Int) { - this.id = id - } +@Entity( + tableName = "ProductVariationEntity", + primaryKeys = ["localSiteId", "remoteProductId", "remoteVariationId"], +) +data class WCProductVariationModel( + val localSiteId: Int = 0, + val remoteProductId: Long = 0L, + val remoteVariationId: Long = 0L, + val dateCreated: String = "", + val dateModified: String = "", + val description: String = "", + val permalink: String = "", + val sku: String = "", + val globalUniqueId: String = "", + val status: String = "", + val price: String = "", + val regularPrice: String = "", + val salePrice: String = "", + val dateOnSaleFrom: String = "", + val dateOnSaleTo: String = "", + val dateOnSaleFromGmt: String = "", + val dateOnSaleToGmt: String = "", + val taxStatus: String = "", // taxable, shipping, none + val taxClass: String = "", + val onSale: Boolean = false, + val purchasable: Boolean = false, + val virtual: Boolean = false, + val downloadable: Boolean = false, + val downloadLimit: Long = 0L, + val downloadExpiry: Int = 0, + val downloads: String = "", // array of downloadable files + val backorders: String = "", // no, notify, yes + val backordersAllowed: Boolean = false, + val backordered: Boolean = false, + val shippingClass: String = "", + val shippingClassId: Int = 0, + val manageStock: Boolean = false, + val stockQuantity: Double = 0.0, + val stockStatus: String = "", + val image: String = "", + val weight: String = "", + val length: String = "", + val width: String = "", + val height: String = "", + val minAllowedQuantity: Int = -1, + val maxAllowedQuantity: Int = -1, + val groupOfQuantity: Int = -1, + val overrideProductQuantities: Boolean = false, + val menuOrder: Int = 0, + val attributes: String = "", + val metadata: String? = null, +) { data class ProductVariantOption( val id: Long? = null, @@ -106,21 +82,15 @@ data class WCProductVariationModel(@PrimaryKey @Column private var id: Int = 0) get() = gson.fromJson(attributes, Array::class.java) fun addVariant(newAttribute: ProductVariantOption) = - mutableListOf() - .apply { - attributeList - ?.takeIf { it.isNotEmpty() } - ?.let { addAll(it) } - add(newAttribute) - }.also { attributes = gson.toJson(it) } - - fun removeVariant(removableAttribute: ProductVariantOption) = - mutableListOf().apply { + mutableListOf() + .apply { attributeList - ?.takeIf { it.isNotEmpty() } - ?.filter { removableAttribute.id != it.id } - ?.let { addAll(it) } - }.also { attributes = gson.toJson(it) } + ?.takeIf { it.isNotEmpty() } + ?.let { addAll(it) } + add(newAttribute) + } + //TODO: fix +// .also { attributes = gson.toJson(it) } /** * Parses the images json array into a list of product images @@ -148,11 +118,4 @@ data class WCProductVariationModel(@PrimaryKey @Column private var id: Int = 0) return null } - /** - * Deserializes the JSON contained in [attributes] into a list of [ProductVariantOption] objects. - */ - fun getProductVariantOptions(): List { - val responseType = object : TypeToken>() {}.type - return gson.fromJson(attributes, responseType) as? List ?: emptyList() - } } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt index 1ce7464c00c..d7f4c03413f 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt @@ -384,20 +384,22 @@ class ProductRestClient @Inject constructor( val productData = response.data if (productData != null) { RemoteVariationPayload( - productData.asProductVariationModel().apply { - this.remoteProductId = remoteProductId - localSiteId = site.id - metadata = stripProductVariationMetaData(metadata) + productData.asProductVariationModel().let { original -> + original.copy( + remoteProductId = remoteProductId, + localSiteId = site.id, + metadata = stripProductVariationMetaData(original.metadata) + ) }, site ) } else { RemoteVariationPayload( ProductError(GENERIC_ERROR, "Success response with empty data"), - WCProductVariationModel().apply { - this.remoteProductId = remoteProductId - this.remoteVariationId = remoteVariationId - }, + WCProductVariationModel().copy ( + remoteProductId = remoteProductId, + remoteVariationId = remoteVariationId + ), site ) } @@ -406,10 +408,10 @@ class ProductRestClient @Inject constructor( is WPAPIResponse.Error -> { RemoteVariationPayload( wpAPINetworkErrorToProductError(response.error), - WCProductVariationModel().apply { - this.remoteProductId = remoteProductId - this.remoteVariationId = remoteVariationId - }, + WCProductVariationModel().copy ( + remoteProductId = remoteProductId, + remoteVariationId = remoteVariationId + ), site ) } @@ -926,10 +928,12 @@ class ProductRestClient @Inject constructor( when (response) { is WPAPIResponse.Success -> { val variationModels = response.data?.map { - it.asProductVariationModel().apply { - localSiteId = site.id - remoteProductId = productId - metadata = stripProductVariationMetaData(metadata) + it.asProductVariationModel().let { original -> + original.copy( + localSiteId = site.id, + remoteProductId = productId, + metadata = stripProductVariationMetaData(original.metadata) + ) } }.orEmpty() @@ -996,12 +1000,13 @@ class ProductRestClient @Inject constructor( return response.toWooPayload { variations -> variations.map { - it.asProductVariationModel() - .apply { - localSiteId = site.id - remoteProductId = productId - metadata = stripProductVariationMetaData(metadata) - } + it.asProductVariationModel().let { original -> + original.copy( + localSiteId = site.id, + remoteProductId = productId, + metadata = stripProductVariationMetaData(original.metadata) + ) + } } } } @@ -1090,19 +1095,21 @@ class ProductRestClient @Inject constructor( return when (response) { is WPAPIResponse.Success -> { response.data?.let { - val newModel = it.asProductVariationModel().apply { - this.remoteProductId = remoteProductId - localSiteId = site.id - metadata = stripProductVariationMetaData(metadata) + val newModel = it.asProductVariationModel().let { original -> + original.copy( + remoteProductId = remoteProductId, + localSiteId = site.id, + metadata = stripProductVariationMetaData(original.metadata) + ) } RemoteUpdateVariationPayload(site, newModel) } ?: RemoteUpdateVariationPayload( ProductError(GENERIC_ERROR, "Success response with empty data"), site, - WCProductVariationModel().apply { - this.remoteProductId = remoteProductId - this.remoteVariationId = remoteVariationId - } + WCProductVariationModel().copy( + remoteProductId = remoteProductId, + remoteVariationId = remoteVariationId + ) ) } @@ -1111,10 +1118,10 @@ class ProductRestClient @Inject constructor( RemoteUpdateVariationPayload( productError, site, - WCProductVariationModel().apply { - this.remoteProductId = remoteProductId - this.remoteVariationId = remoteVariationId - } + WCProductVariationModel().copy( + remoteProductId = remoteProductId, + remoteVariationId = remoteVariationId + ) ) } } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt index 44ec06986a4..c326c873154 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt @@ -64,76 +64,58 @@ class ProductVariationApiResponse : Response { var meta_data: JsonElement? = null @Suppress("ComplexMethod") - fun asProductVariationModel() = - WCProductVariationModel().apply { - val response = this@ProductVariationApiResponse - remoteVariationId = response.id - permalink = response.permalink ?: "" - - dateCreated = response.date_created ?: "" - dateModified = response.date_modified ?: "" - - status = response.status ?: "" - description = response.description ?: "" - sku = response.sku ?: "" - globalUniqueId = response.global_unique_id ?: "" - - price = response.price ?: "" - regularPrice = response.regular_price ?: "" - salePrice = response.sale_price ?: "" - onSale = response.on_sale - - dateOnSaleFrom = response.date_on_sale_from ?: "" - dateOnSaleTo = response.date_on_sale_to ?: "" - dateOnSaleFromGmt = response.date_on_sale_from_gmt ?: "" - dateOnSaleToGmt = response.date_on_sale_to_gmt ?: "" - - taxStatus = response.tax_status ?: "" - taxClass = response.tax_class ?: "" - - backorders = response.backorders ?: "" - backordersAllowed = response.backorders_allowed - backordered = response.backordered - - shippingClass = response.shipping_class ?: "" - shippingClassId = response.shipping_class_id - - downloadLimit = response.download_limit - downloadExpiry = response.download_expiry - - virtual = response.virtual - downloadable = response.downloadable - purchasable = response.purchasable - - manageStock = response.manage_stock - stockQuantity = response.stock_quantity - stockStatus = response.stock_status ?: "" - - attributes = response.attributes?.toString() ?: "" - - weight = response.weight ?: "" - menuOrder = response.menu_order - - attributes = response.attributes?.toString() ?: "" - downloads = response.downloads?.toString() ?: "" - - response.dimensions?.asJsonObject?.let { json -> - length = json.getString("length") ?: "" - width = json.getString("width") ?: "" - height = json.getString("height") ?: "" - } - - image = response.image?.toString() ?: "" - - minAllowedQuantity = response.min_quantity?.toInt() ?: -1 + fun asProductVariationModel(): WCProductVariationModel { + val response = this@ProductVariationApiResponse + val dimensions = response.dimensions?.asJsonObject + return WCProductVariationModel( + remoteVariationId = response.id, + permalink = response.permalink ?: "", + dateCreated = response.date_created ?: "", + dateModified = response.date_modified ?: "", + status = response.status ?: "", + description = response.description ?: "", + sku = response.sku ?: "", + globalUniqueId = response.global_unique_id ?: "", + price = response.price ?: "", + regularPrice = response.regular_price ?: "", + salePrice = response.sale_price ?: "", + onSale = response.on_sale, + dateOnSaleFrom = response.date_on_sale_from ?: "", + dateOnSaleTo = response.date_on_sale_to ?: "", + dateOnSaleFromGmt = response.date_on_sale_from_gmt ?: "", + dateOnSaleToGmt = response.date_on_sale_to_gmt ?: "", + taxStatus = response.tax_status ?: "", + taxClass = response.tax_class ?: "", + backorders = response.backorders ?: "", + backordersAllowed = response.backorders_allowed, + backordered = response.backordered, + shippingClass = response.shipping_class ?: "", + shippingClassId = response.shipping_class_id, + downloadLimit = response.download_limit, + downloadExpiry = response.download_expiry, + virtual = response.virtual, + downloadable = response.downloadable, + purchasable = response.purchasable, + manageStock = response.manage_stock, + stockQuantity = response.stock_quantity, + stockStatus = response.stock_status ?: "", + attributes = response.attributes?.toString() ?: "", + weight = response.weight ?: "", + menuOrder = response.menu_order, + downloads = response.downloads?.toString() ?: "", + length = dimensions?.getString("length") ?: "", + width = dimensions?.getString("width") ?: "", + height = dimensions?.getString("height") ?: "", + image = response.image?.toString() ?: "", + minAllowedQuantity = response.min_quantity?.toInt() ?: -1, maxAllowedQuantity = response.max_quantity?.let { if (it.isEmpty()) "0" else it - }?.toInt() ?: -1 - groupOfQuantity = response.group_of_quantity?.toInt() ?: -1 + }?.toInt() ?: -1, + groupOfQuantity = response.group_of_quantity?.toInt() ?: -1, overrideProductQuantities = response.variation_quantity_rules?.let { it == "yes" - } ?: false - - metadata = response.meta_data?.toString() - } + } ?: false, + metadata = response.meta_data?.toString(), + ) + } } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationMapper.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationMapper.kt index f2c66315f14..fc4ddd5af5d 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationMapper.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationMapper.kt @@ -28,10 +28,10 @@ object ProductVariationMapper { ): HashMap { val body = HashMap() - val storedVariationModel = variationModel ?: WCProductVariationModel().apply { - remoteProductId = updatedVariationModel.remoteProductId + val storedVariationModel = variationModel ?: WCProductVariationModel().copy ( + remoteProductId = updatedVariationModel.remoteProductId, remoteVariationId = updatedVariationModel.remoteVariationId - } + ) if (storedVariationModel.description != updatedVariationModel.description) { body["description"] = updatedVariationModel.description } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt index 99782326a7c..d5dadfb44eb 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt @@ -8,7 +8,6 @@ import com.wellsql.generated.WCProductCategoryModelTable import com.wellsql.generated.WCProductReviewModelTable import com.wellsql.generated.WCProductShippingClassModelTable import com.wellsql.generated.WCProductTagModelTable -import com.wellsql.generated.WCProductVariationModelTable import com.yarolegovich.wellsql.SelectQuery import com.yarolegovich.wellsql.WellSql import kotlinx.coroutines.Dispatchers @@ -103,41 +102,43 @@ internal object ProductSqlUtils { remoteProductId: Long, remoteVariationId: Long ): WCProductVariationModel? { - return WellSql.select(WCProductVariationModel::class.java) - .where().beginGroup() - .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) - .equals(WCProductVariationModelTable.REMOTE_VARIATION_ID, remoteVariationId) - .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) - .endGroup().endWhere() - .asModel.firstOrNull() + return null +// WellSql.select(WCProductVariationModel::class.java) +// .where().beginGroup() +// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) +// .equals(WCProductVariationModelTable.REMOTE_VARIATION_ID, remoteVariationId) +// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) +// .endGroup().endWhere() +// .asModel.firstOrNull() } fun insertOrUpdateProductVariation(variation: WCProductVariationModel): Int { - val result = WellSql.select(WCProductVariationModel::class.java) - .where().beginGroup() - .equals(WCProductVariationModelTable.ID, variation.id) - .or() - .beginGroup() - .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, variation.remoteProductId) - .equals(WCProductVariationModelTable.REMOTE_VARIATION_ID, variation.remoteVariationId) - .equals(WCProductVariationModelTable.LOCAL_SITE_ID, variation.localSiteId) - .endGroup() - .endGroup().endWhere() - .asModel.firstOrNull() - - return if (result == null) { - // Insert - WellSql.insert(variation).execute() - variationsUpdatesTrigger.tryEmit(Unit) - 1 - } else { - // Update - val oldId = result.id - WellSql.update(WCProductVariationModel::class.java).whereId(oldId) - .put(variation, UpdateAllExceptId(WCProductVariationModel::class.java)) - .execute() - .also(::triggerVariationsUpdateIfNeeded) - } +// val result = WellSql.select(WCProductVariationModel::class.java) +// .where().beginGroup() +// .equals(WCProductVariationModelTable.ID, variation.id) +// .or() +// .beginGroup() +// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, variation.remoteProductId) +// .equals(WCProductVariationModelTable.REMOTE_VARIATION_ID, variation.remoteVariationId) +// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, variation.localSiteId) +// .endGroup() +// .endGroup().endWhere() +// .asModel.firstOrNull() +// +// return if (result == null) { +// // Insert +// WellSql.insert(variation).execute() +// variationsUpdatesTrigger.tryEmit(Unit) +// 1 +// } else { +// // Update +// val oldId = result.id +// WellSql.update(WCProductVariationModel::class.java).whereId(oldId) +// .put(variation, UpdateAllExceptId(WCProductVariationModel::class.java)) +// .execute() +// .also(::triggerVariationsUpdateIfNeeded) +// } + return -1 } fun insertOrUpdateProductVariations(variations: List): Int { @@ -151,25 +152,27 @@ internal object ProductSqlUtils { } fun getVariationsForProduct(site: SiteModel, remoteProductId: Long): List { - return WellSql.select(WCProductVariationModel::class.java) - .where() - .beginGroup() - .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) - .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) - .endGroup().endWhere() - .orderBy(WCProductVariationModelTable.MENU_ORDER, SelectQuery.ORDER_ASCENDING) - .asModel +// return WellSql.select(WCProductVariationModel::class.java) +// .where() +// .beginGroup() +// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) +// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) +// .endGroup().endWhere() +// .orderBy(WCProductVariationModelTable.MENU_ORDER, SelectQuery.ORDER_ASCENDING) +// .asModel + return emptyList() } fun deleteVariationsForProduct(site: SiteModel, remoteProductId: Long): Int { - return WellSql.delete(WCProductVariationModel::class.java) - .where().beginGroup() - .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) - .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) - .endGroup() - .endWhere() - .execute() - .also(::triggerVariationsUpdateIfNeeded) +// return WellSql.delete(WCProductVariationModel::class.java) +// .where().beginGroup() +// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) +// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) +// .endGroup() +// .endWhere() +// .execute() +// .also(::triggerVariationsUpdateIfNeeded) + return -1 } fun insertOrUpdateProductReviews(productReviews: List): Int { diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt index f01941e2dd2..231cd03d16f 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt @@ -8,6 +8,7 @@ import androidx.room.RoomDatabase import androidx.room.TypeConverters import androidx.room.withTransaction import org.wordpress.android.fluxc.model.WCProductModel +import org.wordpress.android.fluxc.model.WCProductVariationModel import org.wordpress.android.fluxc.persistence.entity.OrderEntity import org.wordpress.android.fluxc.model.taxes.TaxBasedOnSettingEntity import org.wordpress.android.fluxc.model.taxes.TaxRateEntity @@ -74,7 +75,7 @@ import org.wordpress.android.fluxc.persistence.migrations.MIGRATION_7_8 import org.wordpress.android.fluxc.persistence.migrations.MIGRATION_8_9 import org.wordpress.android.fluxc.persistence.migrations.MIGRATION_9_10 -const val WC_DATABASE_VERSION = 39 +const val WC_DATABASE_VERSION = 40 @Database( version = WC_DATABASE_VERSION, @@ -100,6 +101,7 @@ const val WC_DATABASE_VERSION = 39 ShippingMethodEntity::class, CustomerFromAnalyticsEntity::class, WCProductModel::class, + WCProductVariationModel::class ], autoMigrations = [ AutoMigration(from = 12, to = 13), @@ -121,7 +123,8 @@ const val WC_DATABASE_VERSION = 39 AutoMigration(from = 35, to = 36), AutoMigration(from = 36, to = 37), AutoMigration(from = 37, to = 38, spec = AutoMigration37to38::class), - AutoMigration(from = 38, to = 39) + AutoMigration(from = 38, to = 39), + AutoMigration(from = 39, to = 40) ] ) @TypeConverters( diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 31edf65d108..362132fd6ff 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -1586,10 +1586,10 @@ class WCProductStore @Inject internal constructor( WooResult(result.error) } else { val generatedVariations = result.result?.createdVariations?.map { response -> - response.asProductVariationModel().apply { - remoteProductId = payload.remoteProductId + response.asProductVariationModel().copy( + remoteProductId = payload.remoteProductId, localSiteId = payload.site.id - } + ) } ?: emptyList() ProductSqlUtils.insertOrUpdateProductVariations(generatedVariations) WooResult(result.result) @@ -1622,10 +1622,10 @@ class WCProductStore @Inject internal constructor( WooResult(result.error) } else { val updatedVariations = result.result?.updatedVariations?.map { response -> - response.asProductVariationModel().apply { - remoteProductId = payload.remoteProductId + response.asProductVariationModel().copy( + remoteProductId = payload.remoteProductId, localSiteId = payload.site.id - } + ) } ?: emptyList() ProductSqlUtils.insertOrUpdateProductVariations(updatedVariations) WooResult(result.result) @@ -1928,10 +1928,10 @@ class WCProductStore @Inject internal constructor( result.model ?.createdVariations ?.map { variationResponse -> - variationResponse.asProductVariationModel().apply { - remoteProductId = productId.value + variationResponse.asProductVariationModel().copy( + remoteProductId = productId.value, localSiteId = site.id - } + ) } ?.let { databaseEntities -> ProductSqlUtils.insertOrUpdateProductVariations(databaseEntities) From 5127a0c1653d45fc95a345f69f11937d26825b29 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 17:09:47 +0200 Subject: [PATCH 02/19] Migrate `getVariations` and `observeVariations` to Room's DAO --- .../details/ProductDetailRepository.kt | 2 +- .../android/fluxc/di/WCDatabaseModule.kt | 7 +++-- .../fluxc/persistence/ProductSqlUtils.kt | 21 --------------- .../fluxc/persistence/WCAndroidDatabase.kt | 2 ++ .../persistence/dao/ProductVariationsDao.kt | 27 +++++++++++++++++++ .../android/fluxc/store/WCProductStore.kt | 8 +++--- 6 files changed, 38 insertions(+), 29 deletions(-) create mode 100644 libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailRepository.kt index 6040663e283..182b4849dbf 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailRepository.kt @@ -331,7 +331,7 @@ class ProductDetailRepository @Inject constructor( fun isSkuAvailableLocally(sku: String) = runBlocking { !productStore.isProductExists(selectedSite.get(), sku) } - fun getCachedVariationCount(remoteProductId: Long) = + suspend fun getCachedVariationCount(remoteProductId: Long) = productStore.getVariationsForProduct(selectedSite.get(), remoteProductId).size fun getTaxClassesForSite(): List = diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/di/WCDatabaseModule.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/di/WCDatabaseModule.kt index e072a9f992f..faca6d8ccec 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/di/WCDatabaseModule.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/di/WCDatabaseModule.kt @@ -13,7 +13,6 @@ import org.wordpress.android.fluxc.persistence.dao.AddonsDao import org.wordpress.android.fluxc.persistence.dao.CouponsDao import org.wordpress.android.fluxc.persistence.dao.CustomerFromAnalyticsDao import org.wordpress.android.fluxc.persistence.dao.OrdersDao -import org.wordpress.android.fluxc.persistence.dao.ProductsDao import org.wordpress.android.fluxc.persistence.dao.ShippingMethodDao import javax.inject.Inject import javax.inject.Singleton @@ -76,9 +75,9 @@ interface WCDatabaseModule { return database.customerFromAnalyticsDao } - @Provides internal fun provideProductsDao(database: WCAndroidDatabase): ProductsDao { - return database.productsDao - } + @Provides internal fun provideProductsDao(database: WCAndroidDatabase) = database.productsDao + + @Provides internal fun provideProductVariationsDao(database: WCAndroidDatabase) = database.productVariationsDao } @Binds fun bindTransactionExecutor(database: WCAndroidDatabase): TransactionExecutor } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt index d5dadfb44eb..9a0fc19778d 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt @@ -47,16 +47,6 @@ internal object ProductSqlUtils { private val gson by lazy { Gson() } - fun observeVariations(site: SiteModel, productId: Long): Flow> { - return variationsUpdatesTrigger - .onStart { emit(Unit) } - .debounce(DEBOUNCE_DELAY_FOR_OBSERVERS) - .mapLatest { - getVariationsForProduct(site, productId) - } - .flowOn(Dispatchers.IO) - } - fun observeCategories(site: SiteModel, sortType: ProductCategorySorting): Flow> { return categoriesUpdatesTrigger .onStart { emit(Unit) } @@ -151,17 +141,6 @@ internal object ProductSqlUtils { return rowsAffected } - fun getVariationsForProduct(site: SiteModel, remoteProductId: Long): List { -// return WellSql.select(WCProductVariationModel::class.java) -// .where() -// .beginGroup() -// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) -// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) -// .endGroup().endWhere() -// .orderBy(WCProductVariationModelTable.MENU_ORDER, SelectQuery.ORDER_ASCENDING) -// .asModel - return emptyList() - } fun deleteVariationsForProduct(site: SiteModel, remoteProductId: Long): Int { // return WellSql.delete(WCProductVariationModel::class.java) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt index 231cd03d16f..27c616530ec 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/WCAndroidDatabase.kt @@ -24,6 +24,7 @@ import org.wordpress.android.fluxc.persistence.dao.InboxNotesDao import org.wordpress.android.fluxc.persistence.dao.MetaDataDao import org.wordpress.android.fluxc.persistence.dao.OrderNotesDao import org.wordpress.android.fluxc.persistence.dao.OrdersDao +import org.wordpress.android.fluxc.persistence.dao.ProductVariationsDao import org.wordpress.android.fluxc.persistence.dao.ProductsDao import org.wordpress.android.fluxc.persistence.dao.ShippingMethodDao import org.wordpress.android.fluxc.persistence.dao.TaxBasedOnDao @@ -151,6 +152,7 @@ abstract class WCAndroidDatabase : RoomDatabase(), TransactionExecutor { abstract val shippingMethodDao: ShippingMethodDao abstract val customerFromAnalyticsDao: CustomerFromAnalyticsDao internal abstract val productsDao: ProductsDao + internal abstract val productVariationsDao: ProductVariationsDao companion object { fun buildDb(applicationContext: Context) = Room.databaseBuilder( diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt new file mode 100644 index 00000000000..de33b880233 --- /dev/null +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -0,0 +1,27 @@ +package org.wordpress.android.fluxc.persistence.dao + +import androidx.room.Dao +import androidx.room.Query +import kotlinx.coroutines.flow.Flow +import org.wordpress.android.fluxc.model.WCProductVariationModel + +@Dao +abstract class ProductVariationsDao { + + companion object { + private const val DEFAULT_SELECT_QUERY = """ + SELECT * FROM ProductVariationEntity + WHERE remoteProductId = :remoteProductId + AND localSiteId = :localSiteId + ORDER BY menuOrder ASC + """ + } + + @Query(DEFAULT_SELECT_QUERY) + abstract suspend fun getVariations(localSiteId: Long, remoteProductId: Long): List + + @Query(DEFAULT_SELECT_QUERY) + abstract fun observeVariations(localSiteId: Long, remoteProductId: Long): Flow> + + +} diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 362132fd6ff..c93c4671b82 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -51,6 +51,7 @@ import org.wordpress.android.fluxc.persistence.ProductSqlUtils.getCompositeProdu import org.wordpress.android.fluxc.persistence.ProductSqlUtils.observeBundledProducts import org.wordpress.android.fluxc.persistence.ProductStorageHelper import org.wordpress.android.fluxc.persistence.dao.AddonsDao +import org.wordpress.android.fluxc.persistence.dao.ProductVariationsDao import org.wordpress.android.fluxc.persistence.dao.ProductsDao import org.wordpress.android.fluxc.store.WCProductStore.ProductCategorySorting.NAME_ASC import org.wordpress.android.fluxc.store.WCProductStore.ProductErrorType.GENERIC_ERROR @@ -77,6 +78,7 @@ class WCProductStore @Inject internal constructor( private val productStorageHelper: ProductStorageHelper, private val logger: AppLogWrapper, private val productsDao: ProductsDao, + private val productVariationsDao: ProductVariationsDao ) : Store(dispatcher) { companion object { const val NUM_REVIEWS_PER_FETCH = 25 @@ -838,8 +840,8 @@ class WCProductStore @Inject internal constructor( /** * returns a list of variations for a specific product in the database */ - fun getVariationsForProduct(site: SiteModel, remoteProductId: Long): List = - ProductSqlUtils.getVariationsForProduct(site, remoteProductId) + suspend fun getVariationsForProduct(site: SiteModel, remoteProductId: Long): List = + productVariationsDao.getVariations(localSiteId = site.siteId, remoteProductId = remoteProductId) /** * returns a list of shipping classes for a specific site in the database @@ -1091,7 +1093,7 @@ class WCProductStore @Inject internal constructor( ) fun observeVariations(site: SiteModel, productId: Long): Flow> = - ProductSqlUtils.observeVariations(site, productId) + productVariationsDao.observeVariations(localSiteId = site.siteId, remoteProductId = productId) fun observeCategories( site: SiteModel, From 149d6b46583a85e58791b7653b13550ba6a7da2c Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 17:15:29 +0200 Subject: [PATCH 03/19] Migrate `getVariation` to Room's DAO --- .../variations/VariationDetailRepository.kt | 2 +- .../android/fluxc/persistence/ProductSqlUtils.kt | 15 --------------- .../fluxc/persistence/dao/ProductVariationsDao.kt | 13 +++++++++++++ .../android/fluxc/store/WCProductStore.kt | 8 ++++++-- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailRepository.kt index efcb4e67231..e99a7b86c9e 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailRepository.kt @@ -90,7 +90,7 @@ class VariationDetailRepository @Inject constructor( } } - private fun getCachedWCVariation(remoteProductId: Long, remoteVariationId: Long): WCProductVariationModel? = + private suspend fun getCachedWCVariation(remoteProductId: Long, remoteVariationId: Long): WCProductVariationModel? = productStore.getVariationByRemoteId(selectedSite.get(), remoteProductId, remoteVariationId) suspend fun getQuantityRules(remoteProductId: Long, remoteVariationId: Long): QuantityRules? { diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt index 9a0fc19778d..6e4573970c7 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt @@ -87,21 +87,6 @@ internal object ProductSqlUtils { } } - fun getVariationByRemoteId( - site: SiteModel, - remoteProductId: Long, - remoteVariationId: Long - ): WCProductVariationModel? { - return null -// WellSql.select(WCProductVariationModel::class.java) -// .where().beginGroup() -// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) -// .equals(WCProductVariationModelTable.REMOTE_VARIATION_ID, remoteVariationId) -// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) -// .endGroup().endWhere() -// .asModel.firstOrNull() - } - fun insertOrUpdateProductVariation(variation: WCProductVariationModel): Int { // val result = WellSql.select(WCProductVariationModel::class.java) // .where().beginGroup() diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt index de33b880233..db8e60bae05 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -23,5 +23,18 @@ abstract class ProductVariationsDao { @Query(DEFAULT_SELECT_QUERY) abstract fun observeVariations(localSiteId: Long, remoteProductId: Long): Flow> + @Query( + """ + SELECT * FROM ProductVariationEntity + WHERE remoteProductId = :remoteProductId + AND remoteVariationId = :remoteVariationId + AND localSiteId = :localSiteId + """ + ) + abstract suspend fun getVariation( + localSiteId: Long, + remoteProductId: Long, + remoteVariationId: Long + ): WCProductVariationModel? } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index c93c4671b82..12daf05971c 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -826,12 +826,16 @@ class WCProductStore @Inject internal constructor( /** * returns the corresponding variation from the database as a [WCProductVariationModel]. */ - fun getVariationByRemoteId( + suspend fun getVariationByRemoteId( site: SiteModel, remoteProductId: Long, remoteVariationId: Long ): WCProductVariationModel? = - ProductSqlUtils.getVariationByRemoteId(site, remoteProductId, remoteVariationId) + productVariationsDao.getVariation( + localSiteId = site.siteId, + remoteProductId = remoteProductId, + remoteVariationId = remoteVariationId + ) suspend fun isProductExists(site: SiteModel, sku: String): Boolean { return productsDao.getProduct(site.id, sku = sku) != null From 8370dd46fb28e0ab34e7976665b55b4d517d30c6 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 17:20:33 +0200 Subject: [PATCH 04/19] Migrate `upsertVariation`(s) to Room's DAO --- .../fluxc/persistence/ProductSqlUtils.kt | 40 ------------------- .../persistence/dao/ProductVariationsDao.kt | 7 ++++ .../android/fluxc/store/WCProductStore.kt | 25 +++++------- 3 files changed, 17 insertions(+), 55 deletions(-) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt index 6e4573970c7..d310a489e15 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt @@ -87,46 +87,6 @@ internal object ProductSqlUtils { } } - fun insertOrUpdateProductVariation(variation: WCProductVariationModel): Int { -// val result = WellSql.select(WCProductVariationModel::class.java) -// .where().beginGroup() -// .equals(WCProductVariationModelTable.ID, variation.id) -// .or() -// .beginGroup() -// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, variation.remoteProductId) -// .equals(WCProductVariationModelTable.REMOTE_VARIATION_ID, variation.remoteVariationId) -// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, variation.localSiteId) -// .endGroup() -// .endGroup().endWhere() -// .asModel.firstOrNull() -// -// return if (result == null) { -// // Insert -// WellSql.insert(variation).execute() -// variationsUpdatesTrigger.tryEmit(Unit) -// 1 -// } else { -// // Update -// val oldId = result.id -// WellSql.update(WCProductVariationModel::class.java).whereId(oldId) -// .put(variation, UpdateAllExceptId(WCProductVariationModel::class.java)) -// .execute() -// .also(::triggerVariationsUpdateIfNeeded) -// } - return -1 - } - - fun insertOrUpdateProductVariations(variations: List): Int { - var rowsAffected = 0 - executeInTransaction { - variations.forEach { - rowsAffected += insertOrUpdateProductVariation(it) - } - } - return rowsAffected - } - - fun deleteVariationsForProduct(site: SiteModel, remoteProductId: Long): Int { // return WellSql.delete(WCProductVariationModel::class.java) // .where().beginGroup() diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt index db8e60bae05..fce81e5fe97 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -2,6 +2,7 @@ package org.wordpress.android.fluxc.persistence.dao import androidx.room.Dao import androidx.room.Query +import androidx.room.Upsert import kotlinx.coroutines.flow.Flow import org.wordpress.android.fluxc.model.WCProductVariationModel @@ -37,4 +38,10 @@ abstract class ProductVariationsDao { remoteVariationId: Long ): WCProductVariationModel? + @Upsert + abstract suspend fun upsertProductVariation(variation: WCProductVariationModel) + + @Upsert + abstract suspend fun upsertProductVariations(variations: List) + } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 12daf05971c..1a154e43057 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -790,7 +790,6 @@ class WCProductStore @Inject internal constructor( } class OnVariationUpdated( - var rowsAffected: Int, var remoteProductId: Long, var remoteVariationId: Long ) : OnChanged() { @@ -1142,7 +1141,7 @@ class WCProductStore @Inject internal constructor( .asWooResult() .model?.asProductVariationModel() ?.apply { - ProductSqlUtils.insertOrUpdateProductVariation(this) + productVariationsDao.upsertProductVariation(this) } ?.let { WooResult(it) } } ?: WooResult(WooError(WooErrorType.GENERIC_ERROR, UNKNOWN)) @@ -1160,7 +1159,7 @@ class WCProductStore @Inject internal constructor( .asWooResult() .model?.asProductVariationModel() ?.apply { - ProductSqlUtils.insertOrUpdateProductVariation(this) + productVariationsDao.upsertProductVariation(this) } ?.let { WooResult(it) } ?: WooResult(WooError(INVALID_RESPONSE, GenericErrorType.INVALID_RESPONSE)) @@ -1231,7 +1230,7 @@ class WCProductStore @Inject internal constructor( it.remoteVariationId = result.variation.remoteVariationId } } else { - ProductSqlUtils.insertOrUpdateProductVariation(result.variation) + productVariationsDao.upsertProductVariation(result.variation) OnVariationChanged().also { it.remoteProductId = result.variation.remoteProductId it.remoteVariationId = result.variation.remoteVariationId @@ -1325,7 +1324,7 @@ class WCProductStore @Inject internal constructor( ProductSqlUtils.deleteVariationsForProduct(result.site, result.remoteProductId) } - ProductSqlUtils.insertOrUpdateProductVariations(result.variations) + productVariationsDao.upsertProductVariations(result.variations) OnProductChanged(payload.remoteProductId, canLoadMore = result.canLoadMore) } } @@ -1520,16 +1519,12 @@ class WCProductStore @Inject internal constructor( ) return@withDefaultContext if (result.isError) { OnVariationUpdated( - 0, result.variation.remoteProductId, result.variation.remoteVariationId ).also { it.error = result.error } } else { - val rowsAffected = ProductSqlUtils.insertOrUpdateProductVariation( - result.variation - ) + productVariationsDao.upsertProductVariation(result.variation) OnVariationUpdated( - rowsAffected, result.variation.remoteProductId, result.variation.remoteVariationId ) @@ -1597,7 +1592,7 @@ class WCProductStore @Inject internal constructor( localSiteId = payload.site.id ) } ?: emptyList() - ProductSqlUtils.insertOrUpdateProductVariations(generatedVariations) + productVariationsDao.upsertProductVariations(generatedVariations) WooResult(result.result) } } @@ -1633,7 +1628,7 @@ class WCProductStore @Inject internal constructor( localSiteId = payload.site.id ) } ?: emptyList() - ProductSqlUtils.insertOrUpdateProductVariations(updatedVariations) + productVariationsDao.upsertProductVariations(updatedVariations) WooResult(result.result) } } @@ -1865,7 +1860,7 @@ class WCProductStore @Inject internal constructor( ProductSqlUtils.deleteVariationsForProduct(site, productId) } - ProductSqlUtils.insertOrUpdateProductVariations(response.result) + productVariationsDao.upsertProductVariations(response.result) val canLoadMore = response.result.size == pageSize WooResult(canLoadMore) } @@ -1926,7 +1921,7 @@ class WCProductStore @Inject internal constructor( } } - private fun saveVariationsInDatabase( + private suspend fun saveVariationsInDatabase( result: WooResult, productId: RemoteId, site: SiteModel @@ -1940,7 +1935,7 @@ class WCProductStore @Inject internal constructor( ) } ?.let { databaseEntities -> - ProductSqlUtils.insertOrUpdateProductVariations(databaseEntities) + productVariationsDao.upsertProductVariations(databaseEntities) } } From 1df49aa69f4f15d1bc702f33746cc7003a62e790 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 17:23:19 +0200 Subject: [PATCH 05/19] Migrate `deleteVariation` to Room's DAO --- .../android/fluxc/persistence/ProductSqlUtils.kt | 12 ------------ .../fluxc/persistence/dao/ProductVariationsDao.kt | 11 +++++++++++ .../android/fluxc/store/WCProductStore.kt | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt index d310a489e15..e6a652d0c47 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt @@ -87,18 +87,6 @@ internal object ProductSqlUtils { } } - fun deleteVariationsForProduct(site: SiteModel, remoteProductId: Long): Int { -// return WellSql.delete(WCProductVariationModel::class.java) -// .where().beginGroup() -// .equals(WCProductVariationModelTable.LOCAL_SITE_ID, site.id) -// .equals(WCProductVariationModelTable.REMOTE_PRODUCT_ID, remoteProductId) -// .endGroup() -// .endWhere() -// .execute() -// .also(::triggerVariationsUpdateIfNeeded) - return -1 - } - fun insertOrUpdateProductReviews(productReviews: List): Int { var rowsAffected = 0 executeInTransaction { diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt index fce81e5fe97..4e832993935 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -4,6 +4,7 @@ import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert import kotlinx.coroutines.flow.Flow +import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.WCProductVariationModel @Dao @@ -44,4 +45,14 @@ abstract class ProductVariationsDao { @Upsert abstract suspend fun upsertProductVariations(variations: List) + @Query( + """ + DELETE FROM ProductVariationEntity + WHERE localSiteId = :localSiteId + AND remoteProductId = :remoteProductId + """ + ) + abstract suspend fun deleteVariationsForProduct(localSiteId: Long, remoteProductId: Long) + + } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 1a154e43057..791a77bd115 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -1175,7 +1175,10 @@ class WCProductStore @Inject internal constructor( .asWooResult() .model?.asProductVariationModel() ?.apply { - ProductSqlUtils.deleteVariationsForProduct(site, productId) + productVariationsDao.deleteVariationsForProduct( + localSiteId = site.siteId, + remoteProductId = productId + ) } ?.let { WooResult(it) } ?: WooResult(WooError(INVALID_RESPONSE, GenericErrorType.INVALID_RESPONSE)) @@ -1321,7 +1324,10 @@ class WCProductStore @Inject internal constructor( // delete product variations for site if this is the first page of results, otherwise // product variations deleted outside of the app will persist if (result.offset == 0) { - ProductSqlUtils.deleteVariationsForProduct(result.site, result.remoteProductId) + productVariationsDao.deleteVariationsForProduct( + localSiteId = result.site.siteId, + remoteProductId = payload.remoteProductId + ) } productVariationsDao.upsertProductVariations(result.variations) @@ -1857,7 +1863,10 @@ class WCProductStore @Inject internal constructor( includedVariationIds.isEmpty() && excludedVariationIds.isEmpty() ) { - ProductSqlUtils.deleteVariationsForProduct(site, productId) + productVariationsDao.deleteVariationsForProduct( + localSiteId = site.siteId, + remoteProductId = productId + ) } productVariationsDao.upsertProductVariations(response.result) From 5bee63b9509056c6006de074692738a533842710 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 17:24:15 +0200 Subject: [PATCH 06/19] Remove unused observability wrappers for variations in `ProductSqlUtils` --- .../android/fluxc/persistence/ProductSqlUtils.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt index e6a652d0c47..a9ca5a50102 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/ProductSqlUtils.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onStart @@ -27,22 +26,16 @@ import org.wordpress.android.fluxc.model.WCProductModel import org.wordpress.android.fluxc.model.WCProductReviewModel import org.wordpress.android.fluxc.model.WCProductShippingClassModel import org.wordpress.android.fluxc.model.WCProductTagModel -import org.wordpress.android.fluxc.model.WCProductVariationModel import org.wordpress.android.fluxc.persistence.dao.ProductsDao import org.wordpress.android.fluxc.store.WCProductStore.Companion.DEFAULT_CATEGORY_SORTING -import org.wordpress.android.fluxc.store.WCProductStore.Companion.DEFAULT_PRODUCT_SORTING -import org.wordpress.android.fluxc.store.WCProductStore.Companion.categoryFilter import org.wordpress.android.fluxc.store.WCProductStore.ProductCategorySorting import org.wordpress.android.fluxc.store.WCProductStore.ProductCategorySorting.NAME_ASC import org.wordpress.android.fluxc.store.WCProductStore.ProductCategorySorting.NAME_DESC -import org.wordpress.android.fluxc.store.WCProductStore.ProductFilterOption -import org.wordpress.android.fluxc.store.WCProductStore.ProductSorting import java.util.Locale @Suppress("LargeClass") internal object ProductSqlUtils { private const val DEBOUNCE_DELAY_FOR_OBSERVERS = 50L - private val variationsUpdatesTrigger = MutableSharedFlow(extraBufferCapacity = 1) private val categoriesUpdatesTrigger = MutableSharedFlow(extraBufferCapacity = 1) private val gson by lazy { Gson() } @@ -472,10 +465,6 @@ internal object ProductSqlUtils { } } - private fun triggerVariationsUpdateIfNeeded(affectedRows: Int) { - if (affectedRows != 0) variationsUpdatesTrigger.tryEmit(Unit) - } - private fun triggerCategoriesUpdateIfNeeded(affectedRows: Int) { if (affectedRows != 0) categoriesUpdatesTrigger.tryEmit(Unit) } From 9d12e9d8a91e256bada57535d4e56322b212c223 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 25 Apr 2025 15:19:01 +0200 Subject: [PATCH 07/19] Adjust `WooCommerce` module to WCProductVariationModel changes: update to app model mapper, make query methods suspendable --- .../android/model/ProductVariation.kt | 102 +++++++++++------- .../ProductDetailBottomSheetBuilder.kt | 2 +- .../details/ProductDetailCardBuilder.kt | 11 +- .../variations/VariationRepository.kt | 2 +- .../fluxc/model/WCProductVariationModel.kt | 11 -- 5 files changed, 71 insertions(+), 57 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt index 252b250d098..8fb4f74b6a0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt @@ -134,49 +134,71 @@ open class ProductVariation( } ?: "" } - return (cachedVariation ?: WCProductVariationModel()).also { - it.remoteProductId = remoteProductId - it.remoteVariationId = remoteVariationId - it.sku = sku - it.globalUniqueId = globalUniqueId - it.image = imageToJson() - it.regularPrice = if (regularPrice.isNotSet()) "" else regularPrice.toString() - it.salePrice = if (salePrice.isNotSet()) "" else salePrice.toString() - if (isSaleScheduled) { - saleStartDateGmt?.let { dateOnSaleFrom -> - it.dateOnSaleFromGmt = dateOnSaleFrom.formatToYYYYmmDDhhmmss() - } - it.dateOnSaleToGmt = saleEndDateGmt?.formatToYYYYmmDDhhmmss() ?: "" + fun getDateOnSaleFromGmt(): String { + return if (isSaleScheduled) { + saleStartDateGmt?.formatToYYYYmmDDhhmmss() ?: "" } else { - it.dateOnSaleFromGmt = "" - it.dateOnSaleToGmt = "" + "" } - it.stockStatus = ProductStockStatus.fromStockStatus(stockStatus) - it.backorders = ProductBackorderStatus.fromBackorderStatus(backorderStatus) - it.stockQuantity = stockQuantity - it.purchasable = isPurchasable - it.virtual = isVirtual - it.downloadable = isDownloadable - it.manageStock = isStockManaged - it.description = description - it.status = if (isVisible) PUBLISH.value else PRIVATE.value - it.shippingClass = shippingClass - it.shippingClassId = shippingClassId.toInt() - it.menuOrder = menuOrder - it.attributes = JsonArray().toString() - attributes.takeIf { list -> list.isNotEmpty() } - ?.forEach { variant -> it.addVariant(variant.asSourceModel()) } - it.length = if (length == 0f) "" else length.formatToString() - it.width = if (width == 0f) "" else width.formatToString() - it.weight = if (weight == 0f) "" else weight.formatToString() - it.height = if (height == 0f) "" else height.formatToString() - it.minAllowedQuantity = minAllowedQuantity ?: -1 - it.maxAllowedQuantity = maxAllowedQuantity ?: -1 - it.groupOfQuantity = groupOfQuantity ?: -1 - it.overrideProductQuantities = overrideProductQuantities ?: false - if (this is SubscriptionProductVariation) { + } + + fun getDateOnSaleToGmt(): String { + return if (isSaleScheduled) { + saleEndDateGmt?.formatToYYYYmmDDhhmmss() ?: "" + } else { + "" + } + } + + fun attributesToJson(): String { + val jsonArray = JsonArray() + attributes.forEach { variantOption -> + JsonObject().apply { + addProperty("id", variantOption.id) + addProperty("name", variantOption.name) + addProperty("option", variantOption.option) + }.also { jsonArray.add(it) } + } + return jsonArray.toString() + } + + return (cachedVariation ?: WCProductVariationModel()).copy( + remoteProductId = remoteProductId, + remoteVariationId = remoteVariationId, + sku = sku, + globalUniqueId = globalUniqueId, + image = imageToJson(), + regularPrice = if (regularPrice.isNotSet()) "" else regularPrice.toString(), + salePrice = if (salePrice.isNotSet()) "" else salePrice.toString(), + dateOnSaleFromGmt = getDateOnSaleFromGmt(), + dateOnSaleToGmt = getDateOnSaleToGmt(), + stockStatus = ProductStockStatus.fromStockStatus(stockStatus), + backorders = ProductBackorderStatus.fromBackorderStatus(backorderStatus), + stockQuantity = stockQuantity, + purchasable = isPurchasable, + virtual = isVirtual, + downloadable = isDownloadable, + manageStock = isStockManaged, + description = description, + status = if (isVisible) PUBLISH.value else PRIVATE.value, + shippingClass = shippingClass, + shippingClassId = shippingClassId.toInt(), + menuOrder = menuOrder, + attributes = attributesToJson(), + length = if (length == 0f) "" else length.formatToString(), + width = if (width == 0f) "" else width.formatToString(), + weight = if (weight == 0f) "" else weight.formatToString(), + height = if (height == 0f) "" else height.formatToString(), + minAllowedQuantity = minAllowedQuantity ?: -1, + maxAllowedQuantity = maxAllowedQuantity ?: -1, + groupOfQuantity = groupOfQuantity ?: -1, + overrideProductQuantities = overrideProductQuantities ?: false, + ).let { + if (this@ProductVariation is SubscriptionProductVariation) { // Subscription details are currently the only editable metadata fields from the app. - it.metadata = subscriptionDetails?.toMetadataJson().toString() + it.copy(metadata = subscriptionDetails?.toMetadataJson().toString()) + } else { + it } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailBottomSheetBuilder.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailBottomSheetBuilder.kt index 9e04b75dfad..0440e2425aa 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailBottomSheetBuilder.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailBottomSheetBuilder.kt @@ -105,7 +105,7 @@ class ProductDetailBottomSheetBuilder( } } - private fun ProductAggregate.getShipping(): ProductDetailBottomSheetUiItem? { + private suspend fun ProductAggregate.getShipping(): ProductDetailBottomSheetUiItem? { return if (!product.isVirtual && !hasShipping) { ProductDetailBottomSheetUiItem( ProductDetailBottomSheetType.PRODUCT_SHIPPING, diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt index 3dbdaaa5200..ab44095b6d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilder.kt @@ -85,6 +85,7 @@ import com.woocommerce.android.util.CurrencyFormatter import com.woocommerce.android.util.PriceUtils import com.woocommerce.android.util.StringUtils import com.woocommerce.android.viewmodel.ResourceProvider +import kotlinx.coroutines.runBlocking import java.math.BigDecimal @Suppress("LargeClass", "LongParameterList") @@ -518,9 +519,11 @@ class ProductDetailCardBuilder( subscription?.supportsOneTimeShipping ?: false } else { // For variable subscription products, we need to check against the variations - variationRepository.getProductVariationList(product.remoteId).all { - (it as? SubscriptionProductVariation)?.subscriptionDetails - ?.supportsOneTimeShipping ?: false + runBlocking { + variationRepository.getProductVariationList(product.remoteId).all { + (it as? SubscriptionProductVariation)?.subscriptionDetails + ?.supportsOneTimeShipping ?: false + } } } ) @@ -951,7 +954,7 @@ class ProductDetailCardBuilder( } ) - private fun Product.warning(): ProductProperty? { + private suspend fun Product.warning(): ProductProperty? { val variations = variationRepository.getProductVariationList(this.remoteId) val missingPriceVariation = variations diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationRepository.kt index 65ec39d20a0..5a95e49b95b 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/products/variations/VariationRepository.kt @@ -74,7 +74,7 @@ class VariationRepository @Inject constructor( /** * Returns all product variations for a product and current site that are in the database */ - fun getProductVariationList(remoteProductId: Long): List { + suspend fun getProductVariationList(remoteProductId: Long): List { val product = productStore.getProductByRemoteId(selectedSite.get(), remoteProductId) return productStore.getVariationsForProduct(selectedSite.get(), remoteProductId) .map { diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt index 1e60094e28b..64b27d1d5bb 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt @@ -81,17 +81,6 @@ data class WCProductVariationModel( val attributeList get() = gson.fromJson(attributes, Array::class.java) - fun addVariant(newAttribute: ProductVariantOption) = - mutableListOf() - .apply { - attributeList - ?.takeIf { it.isNotEmpty() } - ?.let { addAll(it) } - add(newAttribute) - } - //TODO: fix -// .also { attributes = gson.toJson(it) } - /** * Parses the images json array into a list of product images */ From 316ef5fae66bb639beaf1e47342e6c26c0875953 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 25 Apr 2025 15:19:55 +0200 Subject: [PATCH 08/19] Fix operations on ProductVariationsDao by passing **local** site id To reduce chance of similar mistake in the future, I propose using `LocalId/RemoteId` wrappers. --- .../persistence/dao/ProductVariationsDao.kt | 13 ++++++------ .../android/fluxc/store/WCProductStore.kt | 20 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt index 4e832993935..dac4c04633c 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -4,7 +4,8 @@ import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert import kotlinx.coroutines.flow.Flow -import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductVariationModel @Dao @@ -20,10 +21,10 @@ abstract class ProductVariationsDao { } @Query(DEFAULT_SELECT_QUERY) - abstract suspend fun getVariations(localSiteId: Long, remoteProductId: Long): List + abstract suspend fun getVariations(localSiteId: LocalId, remoteProductId: RemoteId): List @Query(DEFAULT_SELECT_QUERY) - abstract fun observeVariations(localSiteId: Long, remoteProductId: Long): Flow> + abstract fun observeVariations(localSiteId: LocalId, remoteProductId: RemoteId): Flow> @Query( """ @@ -34,8 +35,8 @@ abstract class ProductVariationsDao { """ ) abstract suspend fun getVariation( - localSiteId: Long, - remoteProductId: Long, + localSiteId: LocalId, + remoteProductId: RemoteId, remoteVariationId: Long ): WCProductVariationModel? @@ -52,7 +53,7 @@ abstract class ProductVariationsDao { AND remoteProductId = :remoteProductId """ ) - abstract suspend fun deleteVariationsForProduct(localSiteId: Long, remoteProductId: Long) + abstract suspend fun deleteVariationsForProduct(localSiteId: LocalId, remoteProductId: RemoteId) } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 791a77bd115..730bbeab731 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -831,8 +831,8 @@ class WCProductStore @Inject internal constructor( remoteVariationId: Long ): WCProductVariationModel? = productVariationsDao.getVariation( - localSiteId = site.siteId, - remoteProductId = remoteProductId, + localSiteId = site.localId(), + remoteProductId = RemoteId(remoteProductId), remoteVariationId = remoteVariationId ) @@ -844,7 +844,7 @@ class WCProductStore @Inject internal constructor( * returns a list of variations for a specific product in the database */ suspend fun getVariationsForProduct(site: SiteModel, remoteProductId: Long): List = - productVariationsDao.getVariations(localSiteId = site.siteId, remoteProductId = remoteProductId) + productVariationsDao.getVariations(localSiteId = site.localId(), remoteProductId = RemoteId(remoteProductId)) /** * returns a list of shipping classes for a specific site in the database @@ -1096,7 +1096,7 @@ class WCProductStore @Inject internal constructor( ) fun observeVariations(site: SiteModel, productId: Long): Flow> = - productVariationsDao.observeVariations(localSiteId = site.siteId, remoteProductId = productId) + productVariationsDao.observeVariations(localSiteId = site.localId(), remoteProductId = RemoteId(productId)) fun observeCategories( site: SiteModel, @@ -1176,8 +1176,8 @@ class WCProductStore @Inject internal constructor( .model?.asProductVariationModel() ?.apply { productVariationsDao.deleteVariationsForProduct( - localSiteId = site.siteId, - remoteProductId = productId + localSiteId = site.localId(), + remoteProductId = RemoteId(productId) ) } ?.let { WooResult(it) } @@ -1325,8 +1325,8 @@ class WCProductStore @Inject internal constructor( // product variations deleted outside of the app will persist if (result.offset == 0) { productVariationsDao.deleteVariationsForProduct( - localSiteId = result.site.siteId, - remoteProductId = payload.remoteProductId + localSiteId = result.site.localId(), + remoteProductId = RemoteId(payload.remoteProductId) ) } @@ -1864,8 +1864,8 @@ class WCProductStore @Inject internal constructor( excludedVariationIds.isEmpty() ) { productVariationsDao.deleteVariationsForProduct( - localSiteId = site.siteId, - remoteProductId = productId + localSiteId = site.localId(), + remoteProductId = RemoteId(productId) ) } From b45f03061cbc15dad45c91a6566bf442e126dc78 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 25 Apr 2025 15:41:57 +0200 Subject: [PATCH 09/19] Fix `ProductTestUtils` from testFixtures To fix lint task --- .../android/fluxc/wc/product/ProductTestUtils.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt b/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt index 41585278ecc..197c624b3d3 100644 --- a/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt +++ b/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt @@ -52,13 +52,13 @@ object ProductTestUtils { status: String = "publish", stockQuantity: Double = 0.0 ): WCProductVariationModel { - return WCProductVariationModel().apply { - remoteProductId = remoteId - remoteVariationId = variationId - localSiteId = siteId - this.status = status - this.stockQuantity = stockQuantity - } + return WCProductVariationModel( + remoteProductId = remoteId, + remoteVariationId = variationId, + localSiteId = siteId, + status = status, + stockQuantity = stockQuantity + ) } fun generateSampleVariations(number: Int, productId: Long, siteId: Int): List { From 1ce9504f0da44f97616da7c5e27783fe50dffb3b Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 25 Apr 2025 16:20:21 +0200 Subject: [PATCH 10/19] Fix building `fluxc-plugin` tests 2 tests are failing right now --- .../android/fluxc/store/WCProductStoreTest.kt | 38 ++++++++++--------- .../leaderboards/WCLeaderboardsStoreTest.kt | 3 +- .../WCProductLeaderboardsMapperTest.kt | 3 +- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt index 02922bb7bd8..671d2a126f3 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt @@ -44,10 +44,10 @@ import org.wordpress.android.fluxc.network.rest.wpcom.wc.product.BatchProductVar import org.wordpress.android.fluxc.network.rest.wpcom.wc.product.CoreProductStockStatus import org.wordpress.android.fluxc.network.rest.wpcom.wc.product.ProductRestClient import org.wordpress.android.fluxc.network.rest.wpcom.wc.product.ProductVariationApiResponse -import org.wordpress.android.fluxc.persistence.ProductSqlUtils import org.wordpress.android.fluxc.persistence.ProductStorageHelper import org.wordpress.android.fluxc.persistence.WCAndroidDatabase import org.wordpress.android.fluxc.persistence.WellSqlConfig +import org.wordpress.android.fluxc.persistence.dao.ProductVariationsDao import org.wordpress.android.fluxc.persistence.dao.ProductsDao import org.wordpress.android.fluxc.store.WCProductStore.BatchGenerateVariationsPayload import org.wordpress.android.fluxc.store.WCProductStore.BatchUpdateProductsPayload @@ -75,6 +75,7 @@ class WCProductStoreTest { private val productRestClient: ProductRestClient = mock() private lateinit var productStore: WCProductStore private lateinit var productsDao: ProductsDao + private lateinit var productsVariationsDao: ProductVariationsDao @Before fun setUp() { @@ -82,7 +83,6 @@ class WCProductStoreTest { val config = SingleStoreWellSqlConfigForTests( appContext, listOf( - WCProductVariationModel::class.java, WCProductCategoryModel::class.java, WCProductReviewModel::class.java, SiteModel::class.java, @@ -98,6 +98,7 @@ class WCProductStoreTest { .build() productsDao = roomDb.productsDao + productsVariationsDao = roomDb.productVariationsDao val productStorageHelper = ProductStorageHelper( productsDao = productsDao, @@ -110,7 +111,8 @@ class WCProductStoreTest { logger = mock(), productStorageHelper = productStorageHelper, coroutineEngine = initCoroutineEngine(), - productsDao = productsDao + productsDao = productsDao, + productVariationsDao = productsVariationsDao ) } @@ -189,9 +191,9 @@ class WCProductStoreTest { @Test fun testUpdateVariation() = runTest { - val variationModel = ProductTestUtils.generateSampleVariation(42, 24).apply { + val variationModel = ProductTestUtils.generateSampleVariation(42, 24).copy( description = "test description" - } + ) val site = SiteModel().apply { id = variationModel.localSiteId } whenever(productRestClient.updateVariation(site, null, variationModel)) .thenReturn(RemoteUpdateVariationPayload(site, variationModel)) @@ -342,23 +344,23 @@ class WCProductStoreTest { val variation = ProductTestUtils.generateSampleVariation( remoteId = 0, variationId = 1 - ).apply { + ).copy ( description = "test new description" - } + ) val site = SiteModel().apply { id = variation.localSiteId } val variations = ProductTestUtils.generateSampleVariations( number = 5, productId = variation.remoteProductId, siteId = site.id ) - ProductSqlUtils.insertOrUpdateProductVariations(variations) + productsVariationsDao.upsertProductVariations(variations) var observedVariations = productStore.observeVariations(site, variation.remoteProductId) .first() assertThat(observedVariations).isEqualTo(variations) // when - ProductSqlUtils.insertOrUpdateProductVariation(variation) + productsVariationsDao.upsertProductVariation(variation) observedVariations = productStore.observeVariations(site, variation.remoteProductId).first() // then @@ -470,7 +472,7 @@ class WCProductStoreTest { productId = product.remoteProductId, siteId = site.id ) - ProductSqlUtils.insertOrUpdateProductVariations(variations) + productsVariationsDao.upsertProductVariations(variations) // when val variationsIds = variations.map { it.remoteVariationId } @@ -503,7 +505,7 @@ class WCProductStoreTest { // then assertThat(result.isError).isFalse assertThat(result.model).isNotNull - with(ProductSqlUtils.getVariationsForProduct(site, product.remoteProductId)) { + with(productsVariationsDao.getVariations(site.localId(), product.remoteId)) { forEach { variation -> assertThat(variation.regularPrice).isEqualTo(newRegularPrice) assertThat(variation.salePrice).isEqualTo(newSalePrice) @@ -523,7 +525,7 @@ class WCProductStoreTest { productId = product.remoteProductId, siteId = site.id ) - ProductSqlUtils.insertOrUpdateProductVariations(variations) + productsVariationsDao.upsertProductVariations(variations) // when val variationsIds = variations.map { it.remoteVariationId } @@ -548,7 +550,7 @@ class WCProductStoreTest { // then assertThat(result.isError).isTrue - with(ProductSqlUtils.getVariationsForProduct(site, product.remoteProductId)) { + with(productsVariationsDao.getVariations(site.localId(), product.remoteId)) { forEach { variation -> assertThat(variation.regularPrice).isNotEqualTo(newRegularPrice) assertThat(variation.salePrice).isNotEqualTo(newSalePrice) @@ -666,7 +668,7 @@ class WCProductStoreTest { assertThat(result.model).isNotNull // then result is saved in DB - with(ProductSqlUtils.getVariationsForProduct(site, productId)) { + with(productsVariationsDao.getVariations(site.localId(), RemoteId(productId))) { assertThat(this.size).isEqualTo(createdVariationsResponse.size) } } @@ -711,7 +713,7 @@ class WCProductStoreTest { assertThat(result.isError).isTrue // then result is NOT saved in DB - with(ProductSqlUtils.getVariationsForProduct(site, productId)) { + with(productsVariationsDao.getVariations(site.localId(), RemoteId(productId))) { assertThat(this.size).isEqualTo(0) } } @@ -760,8 +762,8 @@ class WCProductStoreTest { val site = SiteModel() val productId = RemoteId(123) val variationsToCreate = listOf( - WCProductVariationModel(5).apply { description = "test$id" }, - WCProductVariationModel(6).apply { description = "test$id" } + WCProductVariationModel(5).let { original -> original.copy(description = "test$original.id") }, + WCProductVariationModel(6).let { original -> original.copy(description = "test$original.id") } ) whenever(productRestClient.createVariations(any(), any(), any())) doReturn WooPayload() @@ -799,7 +801,7 @@ class WCProductStoreTest { ) // then - val storedVariations = ProductSqlUtils.getVariationsForProduct(site, productId.value) + val storedVariations = productsVariationsDao.getVariations(site.localId(), productId) assertThat(storedVariations).hasSize(2).anyMatch { it.remoteVariationId == 5L }.anyMatch { diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCLeaderboardsStoreTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCLeaderboardsStoreTest.kt index 64075701377..dcaebccf996 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCLeaderboardsStoreTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCLeaderboardsStoreTest.kt @@ -70,7 +70,8 @@ class WCLeaderboardsStoreTest { mock(), mock(), mock(), - productsDao = database.productsDao + productsDao = database.productsDao, + productVariationsDao = database.productVariationsDao ) ) prepareMocks() diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCProductLeaderboardsMapperTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCProductLeaderboardsMapperTest.kt index 156e727fe84..e6a9f2b2a56 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCProductLeaderboardsMapperTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/wc/leaderboards/WCProductLeaderboardsMapperTest.kt @@ -67,7 +67,8 @@ class WCProductLeaderboardsMapperTest { mock(), mock(), mock(), - productsDao = database.productsDao + productsDao = database.productsDao, + productVariationsDao = database.productVariationsDao ) ) } From ac37d37b473c9b55220eb50d6974d78d5212a26c Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 25 Apr 2025 16:30:22 +0200 Subject: [PATCH 11/19] Ignore variations order in `test that variation insert emits a flow even` Room and WellSql have different defaults for sort. In this test, the order doesn't matter as all items have `menuOrder=0` --- .../org/wordpress/android/fluxc/store/WCProductStoreTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt index 671d2a126f3..78908eb7766 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt @@ -357,14 +357,14 @@ class WCProductStoreTest { var observedVariations = productStore.observeVariations(site, variation.remoteProductId) .first() - assertThat(observedVariations).isEqualTo(variations) + assertThat(observedVariations).containsExactlyInAnyOrderElementsOf(variations) // when productsVariationsDao.upsertProductVariation(variation) observedVariations = productStore.observeVariations(site, variation.remoteProductId).first() // then - assertThat(observedVariations).isEqualTo(variations + variation) + assertThat(observedVariations).containsExactlyInAnyOrderElementsOf(variations + variation) } @Test From d967324a5417f44a1371b75ed64e69b4c1d2fe62 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 28 Apr 2025 15:56:26 +0200 Subject: [PATCH 12/19] Fix WCProductStore unit tests Don't use incorrect `$original.id` for description: instead, use simple string to avoid any confusion. Close database on test end --- .../android/fluxc/store/WCProductStoreTest.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt index 78908eb7766..728f9fd2b51 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt @@ -9,6 +9,7 @@ import junit.framework.TestCase.assertTrue import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -74,6 +75,7 @@ import kotlin.test.assertEquals class WCProductStoreTest { private val productRestClient: ProductRestClient = mock() private lateinit var productStore: WCProductStore + private lateinit var roomDb: WCAndroidDatabase private lateinit var productsDao: ProductsDao private lateinit var productsVariationsDao: ProductVariationsDao @@ -93,7 +95,7 @@ class WCProductStoreTest { WellSql.init(config) config.reset() - val roomDb = Room.inMemoryDatabaseBuilder(appContext, WCAndroidDatabase::class.java) + roomDb = Room.inMemoryDatabaseBuilder(appContext, WCAndroidDatabase::class.java) .allowMainThreadQueries() .build() @@ -762,8 +764,8 @@ class WCProductStoreTest { val site = SiteModel() val productId = RemoteId(123) val variationsToCreate = listOf( - WCProductVariationModel(5).let { original -> original.copy(description = "test$original.id") }, - WCProductVariationModel(6).let { original -> original.copy(description = "test$original.id") } + WCProductVariationModel(5).copy(description = "test1"), + WCProductVariationModel(6).copy(description = "test2") ) whenever(productRestClient.createVariations(any(), any(), any())) doReturn WooPayload() @@ -773,8 +775,8 @@ class WCProductStoreTest { // then verify(productRestClient).createVariations( site = site, productId = productId, variations = listOf( - mapOf("description" to "test5"), - mapOf("description" to "test6") + mapOf("description" to "test1"), + mapOf("description" to "test2") ) ) } @@ -871,4 +873,9 @@ class WCProductStoreTest { IncludeType.fromValue("invalid") ).isNull() } + + @After + fun tearDown() { + roomDb.close() + } } From 0e9cf05426f6ae6b96ded5db14fc2a4fdd627b28 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 28 Apr 2025 15:56:56 +0200 Subject: [PATCH 13/19] Apply code style --- .../android/fluxc/store/WCProductStoreTest.kt | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt index 728f9fd2b51..a716818744a 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt @@ -202,11 +202,13 @@ class WCProductStoreTest { productStore.updateVariation(UpdateVariationPayload(site, variationModel)) - with(productStore.getVariationByRemoteId( + with( + productStore.getVariationByRemoteId( site, variationModel.remoteProductId, variationModel.remoteVariationId - )) { + ) + ) { // The version of the product model in the database should have the updated description assertEquals(variationModel.description, this?.description) // Other fields should not be altered by the update @@ -217,7 +219,7 @@ class WCProductStoreTest { @Test fun testVerifySkuExistsLocally() = runTest { val sku = "woo-cap" - val productModel = ProductTestUtils.generateSampleProduct(42).copy ( + val productModel = ProductTestUtils.generateSampleProduct(42).copy( name = "test product", description = "test description", sku = sku, @@ -236,10 +238,10 @@ class WCProductStoreTest { @Test fun testGetProductsWithFilterOptions() = runTest { val filterOptions = mapOf( - ProductFilterOption.TYPE to "simple", - ProductFilterOption.STOCK_STATUS to "instock", - ProductFilterOption.STATUS to "publish", - ProductFilterOption.CATEGORY to "1337" + ProductFilterOption.TYPE to "simple", + ProductFilterOption.STOCK_STATUS to "instock", + ProductFilterOption.STATUS to "publish", + ProductFilterOption.CATEGORY to "1337" ) val product1 = ProductTestUtils.generateSampleProduct(3) val product2 = ProductTestUtils.generateSampleProduct( @@ -273,7 +275,14 @@ class WCProductStoreTest { categories = "[{\"id\":1337,\"name\":\"Clothing\",\"slug\":\"clothing\"}]" ) - productsDao.upsertProducts(listOf(differentSiteProduct1, differentSiteProduct2, differentSiteProduct3, differentSiteProduct4)) + productsDao.upsertProducts( + listOf( + differentSiteProduct1, + differentSiteProduct2, + differentSiteProduct3, + differentSiteProduct4 + ) + ) // verify that the products for the first site is still 1 assertEquals(1, productStore.getProducts(site, filterOptions).size) @@ -346,7 +355,7 @@ class WCProductStoreTest { val variation = ProductTestUtils.generateSampleVariation( remoteId = 0, variationId = 1 - ).copy ( + ).copy( description = "test new description" ) val site = SiteModel().apply { id = variation.localSiteId } @@ -372,7 +381,7 @@ class WCProductStoreTest { @Test fun `given product review exists, when fetch product review, then local database updated`() = runTest { val site = SiteTestUtils.insertTestAccountAndSiteIntoDb() - val productModel = ProductTestUtils.generateSampleProduct(remoteId = 1).copy ( + val productModel = ProductTestUtils.generateSampleProduct(remoteId = 1).copy( localSiteId = site.localId() ) productsDao.upsertProduct(productModel) @@ -383,7 +392,7 @@ class WCProductStoreTest { site.id ) whenever(productRestClient.fetchProductReviewById(site, reviewModel.remoteProductReviewId)) - .thenReturn(RemoteProductReviewPayload(site, reviewModel)) + .thenReturn(RemoteProductReviewPayload(site, reviewModel)) productStore.fetchSingleProductReview(FetchSingleProductReviewPayload(site, reviewModel.remoteProductReviewId)) @@ -629,14 +638,14 @@ class WCProductStoreTest { val site = SiteModel() val firstVariationAttributes = listOf( ProductVariantOption(id = 1, name = "Size", option = "L"), - ProductVariantOption( id = 2, name = "Color", option = "Blue"), + ProductVariantOption(id = 2, name = "Color", option = "Blue"), ) val secondVariationAttributes = listOf( - ProductVariantOption( id = 1, name = "Size", option = "L"), - ProductVariantOption( id = 2, name = "Color", option = "Red"), + ProductVariantOption(id = 1, name = "Size", option = "L"), + ProductVariantOption(id = 2, name = "Color", option = "Red"), ) - val variations = listOf(firstVariationAttributes,secondVariationAttributes) + val variations = listOf(firstVariationAttributes, secondVariationAttributes) // when API call succeed val variationsPayload = BatchGenerateVariationsPayload( @@ -682,14 +691,14 @@ class WCProductStoreTest { val productId = 6L val site = SiteModel() val firstVariationAttributes = listOf( - ProductVariantOption( id = 1, name = "Size", option = "L"), - ProductVariantOption( id = 2, name = "Color", option = "Blue"), + ProductVariantOption(id = 1, name = "Size", option = "L"), + ProductVariantOption(id = 2, name = "Color", option = "Blue"), ) val secondVariationAttributes = listOf( - ProductVariantOption( id = 1, name = "Size", option = "L"), - ProductVariantOption( id = 2, name = "Color", option = "Red"), + ProductVariantOption(id = 1, name = "Size", option = "L"), + ProductVariantOption(id = 2, name = "Color", option = "Red"), ) - val variations = listOf(firstVariationAttributes,secondVariationAttributes) + val variations = listOf(firstVariationAttributes, secondVariationAttributes) // when API call failed val variationsPayload = BatchGenerateVariationsPayload( @@ -752,7 +761,7 @@ class WCProductStoreTest { // then assertThat(argumentCaptor.allValues).hasSize(1) assertThat(argumentCaptor.allValues.first()).hasSize(4) - argumentCaptor.allValues.first().onEach {(existing, updated) -> + argumentCaptor.allValues.first().onEach { (existing, updated) -> assertThat(existing.remoteProductId).isEqualTo(updated.remoteProductId) } } @@ -821,7 +830,7 @@ class WCProductStoreTest { WCMetaData(1, "key2", "value2"), ) whenever(productRestClient.fetchSingleProduct(site, product.remoteProductId)).thenReturn( - RemoteProductPayload(ProductWithMetaData(product, metadata), site) + RemoteProductPayload(ProductWithMetaData(product, metadata), site) ) // when @@ -835,40 +844,40 @@ class WCProductStoreTest { } @Test - fun `given include_type simple, then return type Simple` () { + fun `given include_type simple, then return type Simple`() { assertThat(IncludeType.fromValue("simple")).isEqualTo(IncludeType.Simple) } @Test - fun `given include_type variable, then return type Variable` () { + fun `given include_type variable, then return type Variable`() { assertThat( IncludeType.fromValue("variable") ).isEqualTo(IncludeType.Variable) } @Test - fun `given include_type external, then return type External` () { + fun `given include_type external, then return type External`() { assertThat( IncludeType.fromValue("external") ).isEqualTo(IncludeType.External) } @Test - fun `given include_type grouped, then return type Grouped` () { + fun `given include_type grouped, then return type Grouped`() { assertThat( IncludeType.fromValue("grouped") ).isEqualTo(IncludeType.Grouped) } @Test - fun `given include_type empty, then return type null` () { + fun `given include_type empty, then return type null`() { assertThat( IncludeType.fromValue("") ).isNull() } @Test - fun `given include_type invalid, then return type null` () { + fun `given include_type invalid, then return type null`() { assertThat( IncludeType.fromValue("invalid") ).isNull() From 968135e217c8724f88ed670280c21f80c1682c01 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 28 Apr 2025 16:36:10 +0200 Subject: [PATCH 14/19] Make the `testJalapenoDebugUnitTest` run --- .../android/ui/products/ProductTestUtils.kt | 24 +++++++++---------- .../ui/products/VariationListViewModelTest.kt | 3 ++- .../ScanToUpdateInventoryViewModelTest.kt | 9 +++---- .../VariationDetailViewModelTest.kt | 5 ++-- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index d0164e1c3a9..dace7f58a3b 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -139,18 +139,18 @@ object ProductTestUtils { isPurchasable: Boolean = true, productAttributes: String = "", ): ProductVariation { - return WCProductVariationModel(2).apply { - dateCreated = "2018-01-05T05:14:30Z" - localSiteId = 1 - remoteProductId = productId - remoteVariationId = variationId - price = amount - image = "" - attributes = productAttributes - virtual = isVirtual - downloadable = isDownloadable - purchasable = isPurchasable - }.toAppModel().also { it.priceWithCurrency = "$10.00" } + return WCProductVariationModel(2).copy( + dateCreated = "2018-01-05T05:14:30Z", + localSiteId = 1, + remoteProductId = productId, + remoteVariationId = variationId, + price = amount, + image = "", + attributes = productAttributes, + virtual = isVirtual, + downloadable = isDownloadable, + purchasable = isPurchasable, + ).toAppModel().also { it.priceWithCurrency = "$10.00" } } fun generateProductVariationList(productId: Long = 1L): List { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt index 24beb6bd1fd..6f87cacc1e6 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt @@ -24,6 +24,7 @@ import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.MultiLiveEvent import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowSnackbar import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -81,7 +82,7 @@ class VariationListViewModelTest : BaseUnitTest() { } @Test - fun `Displays the product variation list view correctly`() { + fun `Displays the product variation list view correctly`() = runTest { doReturn(variations).whenever(variationRepository).getProductVariationList(productRemoteId) createViewModel() diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/inventory/ScanToUpdateInventoryViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/inventory/ScanToUpdateInventoryViewModelTest.kt index 3c42131e6a2..17a7d8d4469 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/inventory/ScanToUpdateInventoryViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/inventory/ScanToUpdateInventoryViewModelTest.kt @@ -331,9 +331,8 @@ class ScanToUpdateInventoryViewModelTest : BaseUnitTest() { whenever(variationRepo.getVariationOrNull(productId, variationId)).thenReturn(originalVariation) whenever(variationRepo.updateVariation(any())).thenReturn( WCProductStore.OnVariationUpdated( - 1, - 1, - variationId + remoteProductId = 1, + remoteVariationId = variationId ) ) whenever( @@ -374,7 +373,9 @@ class ScanToUpdateInventoryViewModelTest : BaseUnitTest() { ProductTestUtils.generateProductVariation(productId = 1, variationId = 2) .copy(stockQuantity = 1.0, isStockManaged = true) whenever(variationRepo.getVariationOrNull(1, 2)).thenReturn(originalVariation) - whenever(variationRepo.updateVariation(any())).thenReturn(WCProductStore.OnVariationUpdated(1, 1, 2)) + whenever( + variationRepo.updateVariation(any()) + ).thenReturn(WCProductStore.OnVariationUpdated(remoteProductId = 1, remoteVariationId = 2)) whenever( resourceProvider.getString( R.string.scan_to_update_inventory_success_snackbar, diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModelTest.kt index 2e5db7778d0..8447bab5d4e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/variations/VariationDetailViewModelTest.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf -import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -168,7 +167,7 @@ class VariationDetailViewModelTest : BaseUnitTest() { @Test fun `Display error message on min-max quantities update product error`() = testBlocking { val displayErrorMessage = "This is an error message" - var result = WCProductStore.OnVariationUpdated(1, 1, 2) + val result = WCProductStore.OnVariationUpdated(remoteProductId = 1, remoteVariationId = 2) result.error = WCProductStore.ProductError( type = WCProductStore.ProductErrorType.INVALID_MIN_MAX_QUANTITY, message = displayErrorMessage @@ -184,7 +183,7 @@ class VariationDetailViewModelTest : BaseUnitTest() { sut.onUpdateButtonClicked() - Assertions.assertThat(showUpdateProductError?.message) + assertThat(showUpdateProductError?.message) .isEqualTo(displayErrorMessage) } From 636b9fadd28a15f6aa631ec1c5d658232f56b86c Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 28 Apr 2025 17:56:52 +0200 Subject: [PATCH 15/19] Fix `WooCommerce` unit tests by mocking suspendable functions to return default values Previously, functions like `VariationRepository#getProductVariationList` or `ProductDetailRepository#getCachedVariationCount` were *not* suspend. This allowed Mockito to return default values for both of the methods, respectively: empty list and 0. After recent changed, both of this functions were refactored to suspend, which caused Mockito to return `null` for them (see: https://github.com/mockito/mockito-kotlin/issues/342) This comment brings back the previous behavior by mocking mentioned methods to return default values. --- .../android/ui/products/VariationListViewModelTest.kt | 5 +++++ .../ui/products/details/ProductDetailCardBuilderTest.kt | 7 ++++++- .../ui/products/details/ProductDetailViewModelTest.kt | 4 +++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt index 6f87cacc1e6..3cfc377cd0e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/VariationListViewModelTest.kt @@ -98,6 +98,9 @@ class VariationListViewModelTest : BaseUnitTest() { fun `Do not fetch product variations from api when not connected`() = testBlocking { doReturn(false).whenever(networkStatus).isConnected() + doReturn( + emptyList() + ).whenever(variationRepository).getProductVariationList(productRemoteId) createViewModel() @@ -210,6 +213,7 @@ class VariationListViewModelTest : BaseUnitTest() { @Test fun `Refresh variations list and hide progress bar if variation generation is successful`() = testBlocking { // given + doReturn(emptyList()).whenever(variationRepository).getProductVariationList(productRemoteId) val variationCandidates = List(5) { id -> listOf(VariantOption(id.toLong(), "Number", id.toString())) } @@ -248,6 +252,7 @@ class VariationListViewModelTest : BaseUnitTest() { // given variationRepository.stub { onBlocking { bulkCreateVariations(any(), any()) } doReturn RequestResult.ERROR + onBlocking { getProductVariationList(productRemoteId) } doReturn emptyList() } val variationCandidates = List(5) { id -> listOf(VariantOption(id.toLong(), "Number", id.toString())) diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt index b76ec4801e6..b67f5ccb7ab 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailCardBuilderTest.kt @@ -12,6 +12,7 @@ import com.woocommerce.android.ui.products.ProductType import com.woocommerce.android.ui.products.addons.AddonRepository import com.woocommerce.android.ui.products.models.ProductProperty import com.woocommerce.android.ui.products.models.ProductPropertyCard +import com.woocommerce.android.ui.products.variations.VariationRepository import com.woocommerce.android.viewmodel.BaseUnitTest import com.woocommerce.android.viewmodel.ResourceProvider import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -61,6 +62,10 @@ class ProductDetailCardBuilderTest : BaseUnitTest() { on { get() } doReturn SiteModel() } + val variationRepository: VariationRepository = mock { + onBlocking { getProductVariationList(any()) } doReturn emptyList() + } + sut = ProductDetailCardBuilder( viewModel = viewModel, selectedSite = selectedSite, @@ -68,7 +73,7 @@ class ProductDetailCardBuilderTest : BaseUnitTest() { currencyFormatter = mock(), parameters = mock(), addonRepository = addonRepo, - variationRepository = mock(), + variationRepository = variationRepository, appPrefsWrapper = mock(), isBlazeEnabled = isBlazeEnabled, isProductCurrentlyPromoted = mock(), diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt index 6c3f26eccc9..9e6e9508096 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/details/ProductDetailViewModelTest.kt @@ -84,7 +84,9 @@ class ProductDetailViewModelTest : BaseUnitTest() { private val wooCommerceStore: WooCommerceStore = mock() private val networkStatus: NetworkStatus = mock() - private val productRepository: ProductDetailRepository = mock() + private val productRepository: ProductDetailRepository = mock { + onBlocking { getCachedVariationCount(any()) } doReturn 0 + } private val productCategoriesRepository: ProductCategoriesRepository = mock() private val productTagsRepository: ProductTagsRepository = mock() private val mediaFilesRepository: MediaFilesRepository = mock() From 4211e37dc37742a6c59d3da7f22a9c62f921f740 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 30 Apr 2025 13:28:10 +0200 Subject: [PATCH 16/19] Drop `WCProductVariationModel` from WellSql --- .../wordpress/android/fluxc/persistence/WellSqlConfig.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt index 639ac2ec0d0..89a8b38b24a 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/persistence/WellSqlConfig.kt @@ -41,7 +41,7 @@ open class WellSqlConfig : DefaultWellConfig { annotation class AddOn override fun getDbVersion(): Int { - return 208 + return 209 } override fun getDbName(): String { @@ -2126,6 +2126,10 @@ open class WellSqlConfig : DefaultWellConfig { 207 -> migrateAddOn(ADDON_WOOCOMMERCE, version) { db.execSQL("DROP TABLE IF EXISTS WCProductModel") } + + 208 -> migrateAddOn(ADDON_WOOCOMMERCE, version) { + db.execSQL("DROP TABLE IF EXISTS WCProductVariationModel") + } } } db.setTransactionSuccessful() From 9b5949c1476a24d6bfab916674be393872f1f3ac Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 30 Apr 2025 13:49:29 +0200 Subject: [PATCH 17/19] Make `ProductVariationsDao` internal --- .../android/fluxc/persistence/dao/ProductVariationsDao.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt index dac4c04633c..f4fdc4e9f14 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -9,7 +9,7 @@ import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductVariationModel @Dao -abstract class ProductVariationsDao { +internal abstract class ProductVariationsDao { companion object { private const val DEFAULT_SELECT_QUERY = """ @@ -54,6 +54,4 @@ abstract class ProductVariationsDao { """ ) abstract suspend fun deleteVariationsForProduct(localSiteId: LocalId, remoteProductId: RemoteId) - - } From a440b6bc96f596ce0de10bd6a6ba029b5d13cc52 Mon Sep 17 00:00:00 2001 From: Petros Paraskevopoulos Date: Thu, 8 May 2025 16:06:20 +0300 Subject: [PATCH 18/19] Fix: Replace payload with result on delete variations for product It looks like as part of 1df49aa69f4f15d1bc702f33746cc7003a62e790 an oversight occurred where, instead of 'result.remoteProductId', 'payload.remoteProductId' got used. I am not sure if that was deliberate or not, and since there was no specific comment about that, it is better to treat that as an oversight. This commit serves as an explicit nudge to reviewers, and the original auhtor, to verify this change with that extra 'fix' commit. --- .../kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 63d0850a3ed..8d84ce66379 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -1326,7 +1326,7 @@ class WCProductStore @Inject internal constructor( if (result.offset == 0) { productVariationsDao.deleteVariationsForProduct( localSiteId = result.site.localId(), - remoteProductId = RemoteId(payload.remoteProductId) + remoteProductId = RemoteId(result.remoteProductId) ) } From 375d22af9d4d3c014db0c2dead3494aa044e0c19 Mon Sep 17 00:00:00 2001 From: Petros Paraskevopoulos Date: Thu, 8 May 2025 17:12:25 +0300 Subject: [PATCH 19/19] Refactor: Use local/remote instead of int/long ids for product variation This follows the same pattern applied to 'WCProductModel' and it seems that this pattern is the one mostly used. This pattern is similar to using a 'value' class to create a more domain-specific type instead of primitive types to help with data class construction clarity. Also, related to: 316ef5fae66bb639beaf1e47342e6c26c0875953 --- .../android/model/ProductVariation.kt | 9 ++--- .../model/SubscriptionProductVariation.kt | 4 +-- .../android/ui/products/ProductTestUtils.kt | 15 ++++---- .../fluxc/model/WCProductVariationModel.kt | 8 +++-- .../wpcom/wc/product/ProductRestClient.kt | 25 ++++++------- .../wc/product/ProductVariationApiResponse.kt | 3 +- .../persistence/dao/ProductVariationsDao.kt | 2 +- .../android/fluxc/store/WCProductStore.kt | 35 ++++++++++--------- .../android/fluxc/store/WCProductStoreTest.kt | 35 ++++++++++--------- .../fluxc/wc/product/ProductTestUtils.kt | 13 +++---- 10 files changed, 79 insertions(+), 70 deletions(-) diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt index 8fb4f74b6a0..f71477f5216 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/ProductVariation.kt @@ -17,6 +17,7 @@ import com.woocommerce.android.ui.products.ProductStatus.PRIVATE import com.woocommerce.android.ui.products.ProductStatus.PUBLISH import com.woocommerce.android.ui.products.ProductStockStatus import kotlinx.parcelize.Parcelize +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductVariationModel import org.wordpress.android.util.DateTimeUtils import java.math.BigDecimal @@ -163,8 +164,8 @@ open class ProductVariation( } return (cachedVariation ?: WCProductVariationModel()).copy( - remoteProductId = remoteProductId, - remoteVariationId = remoteVariationId, + remoteProductId = RemoteId(remoteProductId), + remoteVariationId = RemoteId(remoteVariationId), sku = sku, globalUniqueId = globalUniqueId, image = imageToJson(), @@ -305,8 +306,8 @@ data class VariantOption( fun WCProductVariationModel.toAppModel(): ProductVariation { return ProductVariation( - remoteProductId = this.remoteProductId, - remoteVariationId = this.remoteVariationId, + remoteProductId = this.remoteProductId.value, + remoteVariationId = this.remoteVariationId.value, sku = this.sku, globalUniqueId = this.globalUniqueId, image = this.getImageModel()?.let { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/SubscriptionProductVariation.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/SubscriptionProductVariation.kt index cb48bb1646c..5af29fab90d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/model/SubscriptionProductVariation.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/model/SubscriptionProductVariation.kt @@ -88,8 +88,8 @@ class SubscriptionProductVariation( constructor(model: WCProductVariationModel) : this( subscriptionDetails = model.metadata?.let { SubscriptionDetailsMapper.toAppModel(it) }, - remoteProductId = model.remoteProductId, - remoteVariationId = model.remoteVariationId, + remoteProductId = model.remoteProductId.value, + remoteVariationId = model.remoteVariationId.value, sku = model.sku, globalUniqueId = model.globalUniqueId, image = model.getImageModel()?.let { diff --git a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt index dace7f58a3b..c0b095a667e 100644 --- a/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt +++ b/WooCommerce/src/test/kotlin/com/woocommerce/android/ui/products/ProductTestUtils.kt @@ -8,7 +8,8 @@ import com.woocommerce.android.model.ProductVariation import com.woocommerce.android.model.toAppModel import com.woocommerce.android.ui.products.ProductStatus.DRAFT import org.intellij.lang.annotations.Language -import org.wordpress.android.fluxc.model.LocalOrRemoteId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductModel import org.wordpress.android.fluxc.model.WCProductVariationModel import java.sql.Date @@ -62,8 +63,8 @@ object ProductTestUtils { ): Product { return WCProductModel( dateCreated = "2018-01-05T05:14:30Z", - localSiteId = LocalOrRemoteId.LocalId(2), - remoteId = LocalOrRemoteId.RemoteId(productId), + localSiteId = LocalId(2), + remoteId = RemoteId(productId), parentId = parentID, status = customStatus ?: "publish", type = productType ?: if (isVariable) "variable" else "simple", @@ -139,11 +140,11 @@ object ProductTestUtils { isPurchasable: Boolean = true, productAttributes: String = "", ): ProductVariation { - return WCProductVariationModel(2).copy( + return WCProductVariationModel(LocalId(2)).copy( dateCreated = "2018-01-05T05:14:30Z", - localSiteId = 1, - remoteProductId = productId, - remoteVariationId = variationId, + localSiteId = LocalId(1), + remoteProductId = RemoteId(productId), + remoteVariationId = RemoteId(variationId), price = amount, image = "", attributes = productAttributes, diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt index 64b27d1d5bb..19c9ee73f55 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/model/WCProductVariationModel.kt @@ -4,6 +4,8 @@ import androidx.room.Entity import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.JsonParseException +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductVariationModel.ProductVariantOption import org.wordpress.android.fluxc.network.utils.getLong import org.wordpress.android.fluxc.network.utils.getString @@ -22,9 +24,9 @@ typealias VariationAttributes = List primaryKeys = ["localSiteId", "remoteProductId", "remoteVariationId"], ) data class WCProductVariationModel( - val localSiteId: Int = 0, - val remoteProductId: Long = 0L, - val remoteVariationId: Long = 0L, + val localSiteId: LocalId = LocalId(0), + val remoteProductId: RemoteId = RemoteId(0), + val remoteVariationId: RemoteId = RemoteId(0), val dateCreated: String = "", val dateModified: String = "", val description: String = "", diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt index cc6ea672c43..84bef3593c7 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductRestClient.kt @@ -9,6 +9,7 @@ import org.wordpress.android.fluxc.generated.WCProductActionBuilder import org.wordpress.android.fluxc.generated.endpoint.WOOCOMMERCE import org.wordpress.android.fluxc.generated.endpoint.WPAPI import org.wordpress.android.fluxc.generated.endpoint.WPCOMREST +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.ProductWithMetaData import org.wordpress.android.fluxc.model.SiteModel @@ -386,8 +387,8 @@ class ProductRestClient @Inject constructor( RemoteVariationPayload( productData.asProductVariationModel().let { original -> original.copy( - remoteProductId = remoteProductId, - localSiteId = site.id, + remoteProductId = RemoteId(remoteProductId), + localSiteId = LocalId(site.id), metadata = stripProductVariationMetaData(original.metadata) ) }, @@ -397,8 +398,8 @@ class ProductRestClient @Inject constructor( RemoteVariationPayload( ProductError(GENERIC_ERROR, "Success response with empty data"), WCProductVariationModel().copy ( - remoteProductId = remoteProductId, - remoteVariationId = remoteVariationId + remoteProductId = RemoteId(remoteProductId), + remoteVariationId = RemoteId(remoteVariationId) ), site ) @@ -409,8 +410,8 @@ class ProductRestClient @Inject constructor( RemoteVariationPayload( wpAPINetworkErrorToProductError(response.error), WCProductVariationModel().copy ( - remoteProductId = remoteProductId, - remoteVariationId = remoteVariationId + remoteProductId = RemoteId(remoteProductId), + remoteVariationId = RemoteId(remoteVariationId) ), site ) @@ -934,8 +935,8 @@ class ProductRestClient @Inject constructor( val variationModels = response.data?.map { it.asProductVariationModel().let { original -> original.copy( - localSiteId = site.id, - remoteProductId = productId, + localSiteId = LocalId(site.id), + remoteProductId = RemoteId(productId), metadata = stripProductVariationMetaData(original.metadata) ) } @@ -1006,8 +1007,8 @@ class ProductRestClient @Inject constructor( variations.map { it.asProductVariationModel().let { original -> original.copy( - localSiteId = site.id, - remoteProductId = productId, + localSiteId = LocalId(site.id), + remoteProductId = RemoteId(productId), metadata = stripProductVariationMetaData(original.metadata) ) } @@ -1083,7 +1084,7 @@ class ProductRestClient @Inject constructor( ): RemoteUpdateVariationPayload { val remoteProductId = updatedProductVariationModel.remoteProductId val remoteVariationId = updatedProductVariationModel.remoteVariationId - val url = WOOCOMMERCE.products.id(remoteProductId).variations.variation(remoteVariationId).pathV3 + val url = WOOCOMMERCE.products.id(remoteProductId.value).variations.variation(remoteVariationId.value).pathV3 val body = variantModelToProductJsonBody( storedWCProductVariationModel, updatedProductVariationModel @@ -1102,7 +1103,7 @@ class ProductRestClient @Inject constructor( val newModel = it.asProductVariationModel().let { original -> original.copy( remoteProductId = remoteProductId, - localSiteId = site.id, + localSiteId = LocalId(site.id), metadata = stripProductVariationMetaData(original.metadata) ) } diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt index c326c873154..47283059a6c 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/network/rest/wpcom/wc/product/ProductVariationApiResponse.kt @@ -1,6 +1,7 @@ package org.wordpress.android.fluxc.network.rest.wpcom.wc.product import com.google.gson.JsonElement +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductVariationModel import org.wordpress.android.fluxc.network.Response import org.wordpress.android.fluxc.network.utils.getString @@ -68,7 +69,7 @@ class ProductVariationApiResponse : Response { val response = this@ProductVariationApiResponse val dimensions = response.dimensions?.asJsonObject return WCProductVariationModel( - remoteVariationId = response.id, + remoteVariationId = RemoteId(response.id), permalink = response.permalink ?: "", dateCreated = response.date_created ?: "", dateModified = response.date_modified ?: "", diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt index f4fdc4e9f14..47694209090 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/persistence/dao/ProductVariationsDao.kt @@ -37,7 +37,7 @@ internal abstract class ProductVariationsDao { abstract suspend fun getVariation( localSiteId: LocalId, remoteProductId: RemoteId, - remoteVariationId: Long + remoteVariationId: RemoteId ): WCProductVariationModel? @Upsert diff --git a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt index 8d84ce66379..fab54248263 100644 --- a/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt +++ b/libs/fluxc-plugin/src/main/kotlin/org/wordpress/android/fluxc/store/WCProductStore.kt @@ -13,6 +13,7 @@ import org.wordpress.android.fluxc.Payload import org.wordpress.android.fluxc.action.WCProductAction import org.wordpress.android.fluxc.annotations.action.Action import org.wordpress.android.fluxc.domain.Addon +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.ProductWithMetaData import org.wordpress.android.fluxc.model.SiteModel @@ -833,7 +834,7 @@ class WCProductStore @Inject internal constructor( productVariationsDao.getVariation( localSiteId = site.localId(), remoteProductId = RemoteId(remoteProductId), - remoteVariationId = remoteVariationId + remoteVariationId = RemoteId(remoteVariationId) ) suspend fun isProductExists(site: SiteModel, sku: String): Boolean { @@ -1229,14 +1230,14 @@ class WCProductStore @Inject internal constructor( return@withDefaultContext if (result.isError) { OnVariationChanged().also { it.error = result.error - it.remoteProductId = result.variation.remoteProductId - it.remoteVariationId = result.variation.remoteVariationId + it.remoteProductId = result.variation.remoteProductId.value + it.remoteVariationId = result.variation.remoteVariationId.value } } else { productVariationsDao.upsertProductVariation(result.variation) OnVariationChanged().also { - it.remoteProductId = result.variation.remoteProductId - it.remoteVariationId = result.variation.remoteVariationId + it.remoteProductId = result.variation.remoteProductId.value + it.remoteVariationId = result.variation.remoteVariationId.value } } } @@ -1515,8 +1516,8 @@ class WCProductStore @Inject internal constructor( with(payload) { val storedVariation = getVariationByRemoteId( site, - variation.remoteProductId, - variation.remoteVariationId + variation.remoteProductId.value, + variation.remoteVariationId.value ) val result: RemoteUpdateVariationPayload = wcProductRestClient.updateVariation( site, @@ -1525,14 +1526,14 @@ class WCProductStore @Inject internal constructor( ) return@withDefaultContext if (result.isError) { OnVariationUpdated( - result.variation.remoteProductId, - result.variation.remoteVariationId + result.variation.remoteProductId.value, + result.variation.remoteVariationId.value ).also { it.error = result.error } } else { productVariationsDao.upsertProductVariation(result.variation) OnVariationUpdated( - result.variation.remoteProductId, - result.variation.remoteVariationId + result.variation.remoteProductId.value, + result.variation.remoteVariationId.value ) } } @@ -1594,8 +1595,8 @@ class WCProductStore @Inject internal constructor( } else { val generatedVariations = result.result?.createdVariations?.map { response -> response.asProductVariationModel().copy( - remoteProductId = payload.remoteProductId, - localSiteId = payload.site.id + remoteProductId = RemoteId(payload.remoteProductId), + localSiteId = LocalId(payload.site.id) ) } ?: emptyList() productVariationsDao.upsertProductVariations(generatedVariations) @@ -1630,8 +1631,8 @@ class WCProductStore @Inject internal constructor( } else { val updatedVariations = result.result?.updatedVariations?.map { response -> response.asProductVariationModel().copy( - remoteProductId = payload.remoteProductId, - localSiteId = payload.site.id + remoteProductId = RemoteId(payload.remoteProductId), + localSiteId = LocalId(payload.site.id) ) } ?: emptyList() productVariationsDao.upsertProductVariations(updatedVariations) @@ -1980,8 +1981,8 @@ class WCProductStore @Inject internal constructor( ?.createdVariations ?.map { variationResponse -> variationResponse.asProductVariationModel().copy( - remoteProductId = productId.value, - localSiteId = site.id + remoteProductId = productId, + localSiteId = LocalId(site.id) ) } ?.let { databaseEntities -> diff --git a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt index a716818744a..d0457588894 100644 --- a/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt +++ b/libs/fluxc-plugin/src/test/java/org/wordpress/android/fluxc/store/WCProductStoreTest.kt @@ -28,6 +28,7 @@ import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.SingleStoreWellSqlConfigForTests import org.wordpress.android.fluxc.generated.WCProductActionBuilder import org.wordpress.android.fluxc.model.AccountModel +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.ProductWithMetaData import org.wordpress.android.fluxc.model.SiteModel @@ -196,7 +197,7 @@ class WCProductStoreTest { val variationModel = ProductTestUtils.generateSampleVariation(42, 24).copy( description = "test description" ) - val site = SiteModel().apply { id = variationModel.localSiteId } + val site = SiteModel().apply { id = variationModel.localSiteId.value } whenever(productRestClient.updateVariation(site, null, variationModel)) .thenReturn(RemoteUpdateVariationPayload(site, variationModel)) @@ -205,8 +206,8 @@ class WCProductStoreTest { with( productStore.getVariationByRemoteId( site, - variationModel.remoteProductId, - variationModel.remoteVariationId + variationModel.remoteProductId.value, + variationModel.remoteVariationId.value ) ) { // The version of the product model in the database should have the updated description @@ -358,21 +359,21 @@ class WCProductStoreTest { ).copy( description = "test new description" ) - val site = SiteModel().apply { id = variation.localSiteId } + val site = SiteModel().apply { id = variation.localSiteId.value } val variations = ProductTestUtils.generateSampleVariations( number = 5, - productId = variation.remoteProductId, + productId = variation.remoteProductId.value, siteId = site.id ) productsVariationsDao.upsertProductVariations(variations) - var observedVariations = productStore.observeVariations(site, variation.remoteProductId) + var observedVariations = productStore.observeVariations(site, variation.remoteProductId.value) .first() assertThat(observedVariations).containsExactlyInAnyOrderElementsOf(variations) // when productsVariationsDao.upsertProductVariation(variation) - observedVariations = productStore.observeVariations(site, variation.remoteProductId).first() + observedVariations = productStore.observeVariations(site, variation.remoteProductId.value).first() // then assertThat(observedVariations).containsExactlyInAnyOrderElementsOf(variations + variation) @@ -413,7 +414,7 @@ class WCProductStoreTest { ) // when - val variationsIds = variations.map { it.remoteVariationId } + val variationsIds = variations.map { it.remoteVariationId.value } val variationsUpdatePayload = BatchUpdateVariationsPayload.Builder( site, product.remoteProductId, @@ -450,7 +451,7 @@ class WCProductStoreTest { ) // when - val variationsIds = variations.map { it.remoteVariationId } + val variationsIds = variations.map { it.remoteVariationId.value } val variationsUpdatePayload = BatchUpdateVariationsPayload.Builder(site, product.remoteProductId, variationsIds) .build() @@ -486,7 +487,7 @@ class WCProductStoreTest { productsVariationsDao.upsertProductVariations(variations) // when - val variationsIds = variations.map { it.remoteVariationId } + val variationsIds = variations.map { it.remoteVariationId.value } val newRegularPrice = "1.234 💰" val newSalePrice = "0.234 💰" val variationsUpdatePayload = @@ -496,7 +497,7 @@ class WCProductStoreTest { .build() val variationsReturnedFromBackend = variations.map { ProductVariationApiResponse().apply { - id = it.remoteVariationId + id = it.remoteVariationId.value regular_price = newRegularPrice sale_price = newSalePrice } @@ -539,7 +540,7 @@ class WCProductStoreTest { productsVariationsDao.upsertProductVariations(variations) // when - val variationsIds = variations.map { it.remoteVariationId } + val variationsIds = variations.map { it.remoteVariationId.value } val newRegularPrice = "1.234 💰" val newSalePrice = "0.234 💰" val variationsUpdatePayload = @@ -582,7 +583,7 @@ class WCProductStoreTest { val builder = BatchUpdateVariationsPayload.Builder( site, product.remoteProductId, - variations.map { it.remoteVariationId } + variations.map { it.remoteVariationId.value } ) val modifiedRegularPrice = "11234.234" @@ -773,8 +774,8 @@ class WCProductStoreTest { val site = SiteModel() val productId = RemoteId(123) val variationsToCreate = listOf( - WCProductVariationModel(5).copy(description = "test1"), - WCProductVariationModel(6).copy(description = "test2") + WCProductVariationModel(LocalId(5)).copy(description = "test1"), + WCProductVariationModel(LocalId(6)).copy(description = "test2") ) whenever(productRestClient.createVariations(any(), any(), any())) doReturn WooPayload() @@ -814,9 +815,9 @@ class WCProductStoreTest { // then val storedVariations = productsVariationsDao.getVariations(site.localId(), productId) assertThat(storedVariations).hasSize(2).anyMatch { - it.remoteVariationId == 5L + it.remoteVariationId == RemoteId(5L) }.anyMatch { - it.remoteVariationId == 6L + it.remoteVariationId == RemoteId(6L) } } diff --git a/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt b/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt index 197c624b3d3..0e3db576d07 100644 --- a/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt +++ b/libs/fluxc-plugin/src/testFixtures/kotlin/org/wordpress/android/fluxc/wc/product/ProductTestUtils.kt @@ -3,7 +3,8 @@ package org.wordpress.android.fluxc.wc.product import com.google.gson.Gson import com.google.gson.reflect.TypeToken import org.wordpress.android.fluxc.UnitTestUtils -import org.wordpress.android.fluxc.model.LocalOrRemoteId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId +import org.wordpress.android.fluxc.model.LocalOrRemoteId.RemoteId import org.wordpress.android.fluxc.model.WCProductCategoryModel import org.wordpress.android.fluxc.model.WCProductModel import org.wordpress.android.fluxc.model.WCProductReviewModel @@ -31,8 +32,8 @@ object ProductTestUtils { shortDescription: String = "", ): WCProductModel { return WCProductModel().copy( - remoteId = LocalOrRemoteId.RemoteId(remoteId), - localSiteId = LocalOrRemoteId.LocalId(siteId), + remoteId = RemoteId(remoteId), + localSiteId = LocalId(siteId), type = type, name = name, virtual = virtual, @@ -53,9 +54,9 @@ object ProductTestUtils { stockQuantity: Double = 0.0 ): WCProductVariationModel { return WCProductVariationModel( - remoteProductId = remoteId, - remoteVariationId = variationId, - localSiteId = siteId, + remoteProductId = RemoteId(remoteId), + remoteVariationId = RemoteId(variationId), + localSiteId = LocalId(siteId), status = status, stockQuantity = stockQuantity )