From 9f715a424a6dac967455ed35c9bd1bfd873f2fa1 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Thu, 24 Apr 2025 16:55:51 +0200 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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 076f1430358d085bc7b171835b8492e1415c09ff Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 12:01:52 +0200 Subject: [PATCH 10/18] Fix missing buildkite test collector support when JaCoCo fails In case when `jacocoTestReport` task fails, our script was not moving test results to the correct directory, as it was exiting on error before collection. --- .buildkite/commands/run-unit-tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index a464acf103c..9c615edcaa2 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -33,11 +33,11 @@ else annotate_test_failures "$results_file" fi +echo "--- 🧪 Copying test logs for test collector" +mkdir WooCommerce/build/buildkite-test-analytics && cp "$results_file" WooCommerce/build/buildkite-test-analytics + echo "--- ⚒️ Generating and uploading code coverage" ./gradlew jacocoTestReport .buildkite/commands/upload-code-coverage.sh -echo "--- 🧪 Copying test logs for test collector" -mkdir WooCommerce/build/buildkite-test-analytics && cp "$results_file" WooCommerce/build/buildkite-test-analytics - exit $TESTS_EXIT_STATUS From 9d416dbbf9038ed306cd460efe0edc6e417c7ac5 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 12:53:30 +0200 Subject: [PATCH 11/18] Run `jacocoTestReport` on the same Gradle invocation. Upload code coverage only on success tests This will reduce chances of `testJalapenoDebugUnitTest` task being not up to date when running `jacocoTestReport`, which caused running unit tests again, without any reason. --- .buildkite/commands/run-unit-tests.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index 9c615edcaa2..123bfb6c623 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -10,7 +10,7 @@ bundle exec fastlane run configure_apply echo "--- 🧪 Testing" set +e -./gradlew testJalapenoDebugUnitTest testDebugUnitTest +./gradlew testJalapenoDebugUnitTest testDebugUnitTest jacocoTestReport TESTS_EXIT_STATUS=$? set -e @@ -18,9 +18,11 @@ if [[ "$TESTS_EXIT_STATUS" -ne 0 ]]; then # Keep the (otherwise collapsed) current "Testing" section open in Buildkite logs on error. See https://buildkite.com/docs/pipelines/managing-log-output#collapsing-output echo "^^^ +++" echo "Unit Tests failed!" +else + echo "--- ⚒️ Uploading code coverage" + .buildkite/commands/upload-code-coverage.sh fi - echo "--- 🚦 Report Tests Status" results_file="WooCommerce/build/test-results/merged-test-results.xml" # Merge JUnit results into a single file (for performance reasons with reporting) @@ -36,8 +38,4 @@ fi echo "--- 🧪 Copying test logs for test collector" mkdir WooCommerce/build/buildkite-test-analytics && cp "$results_file" WooCommerce/build/buildkite-test-analytics -echo "--- ⚒️ Generating and uploading code coverage" -./gradlew jacocoTestReport -.buildkite/commands/upload-code-coverage.sh - exit $TESTS_EXIT_STATUS From 8bec35571e4b54a35e2c223d0b20c0d84060bec4 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 13:17:58 +0200 Subject: [PATCH 12/18] Add missing modules test tasks to JacocoReport dependencies To address issues like: Reason: Task ':jacocoTestReport' uses this output of task ':libs:apifaker:testDebugUnitTest' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. --- config/gradle/jacoco.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/gradle/jacoco.gradle b/config/gradle/jacoco.gradle index 0e81ca6b00c..ea8422a255d 100644 --- a/config/gradle/jacoco.gradle +++ b/config/gradle/jacoco.gradle @@ -16,6 +16,8 @@ rootProject.afterEvaluate { ':libs:fluxc-plugin:testDebugUnitTest', ':libs:fluxc-tests:testDebugUnitTest', ':libs:login:testDebugUnitTest', + ':libs:apifaker:testDebugUnitTest', + ':libs:commons:testDebugUnitTest', ) group = "Reporting" From 6c958c162e216ff98b227afaaf4e1c507b49afa0 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 14:01:08 +0200 Subject: [PATCH 13/18] Print "Uploading code coverage" in a new line --- .buildkite/commands/run-unit-tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index 123bfb6c623..d4cca085fa4 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -19,6 +19,7 @@ if [[ "$TESTS_EXIT_STATUS" -ne 0 ]]; then echo "^^^ +++" echo "Unit Tests failed!" else + echo echo "--- ⚒️ Uploading code coverage" .buildkite/commands/upload-code-coverage.sh fi From ca437a01b5413ac0357f60b4bf23ab68a47ac678 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 14:25:21 +0200 Subject: [PATCH 14/18] Open `Testing` by default There are two reasons for this: - with the current setup, we were opening "Copying test logs for test collector" if tests failed, because this was the last collapsed regular group (docs: https://buildkite.com/docs/pipelines/configure/managing-log-output#collapsing-output) and it was confusing: tests did fail, not copying test logs - developers are much more interested in details of `Testing` step than (empty) details of "Copying test logs" so it makes sense to open former by default --- .buildkite/commands/run-unit-tests.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index d4cca085fa4..26d54ac92a5 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -8,19 +8,14 @@ install_gems echo "--- :closed_lock_with_key: Installing Secrets" bundle exec fastlane run configure_apply -echo "--- 🧪 Testing" +echo "+++ 🧪 Testing" set +e ./gradlew testJalapenoDebugUnitTest testDebugUnitTest jacocoTestReport TESTS_EXIT_STATUS=$? set -e -if [[ "$TESTS_EXIT_STATUS" -ne 0 ]]; then - # Keep the (otherwise collapsed) current "Testing" section open in Buildkite logs on error. See https://buildkite.com/docs/pipelines/managing-log-output#collapsing-output - echo "^^^ +++" - echo "Unit Tests failed!" -else - echo - echo "--- ⚒️ Uploading code coverage" +if [[ "$TESTS_EXIT_STATUS" -eq 0 ]]; then + echo -e "--- ⚒️ Uploading code coverage" .buildkite/commands/upload-code-coverage.sh fi From 6ff44ee9fe9fe000e3bde52f662f908e32278637 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 14:39:24 +0200 Subject: [PATCH 15/18] echo report tests status group in a new line --- .buildkite/commands/run-unit-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index 26d54ac92a5..2faf63895a0 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -19,7 +19,7 @@ if [[ "$TESTS_EXIT_STATUS" -eq 0 ]]; then .buildkite/commands/upload-code-coverage.sh fi -echo "--- 🚦 Report Tests Status" +echo -e "--- 🚦 Report Tests Status" results_file="WooCommerce/build/test-results/merged-test-results.xml" # Merge JUnit results into a single file (for performance reasons with reporting) # See https://github.com/woocommerce/woocommerce-android/pull/12064 From a5cc82beb6714f67a7ccc73d0f7c60f119cf3070 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 29 Apr 2025 15:00:48 +0200 Subject: [PATCH 16/18] Actually print some section headers in a new line --- .buildkite/commands/run-unit-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.buildkite/commands/run-unit-tests.sh b/.buildkite/commands/run-unit-tests.sh index 2faf63895a0..ba9413a8f32 100755 --- a/.buildkite/commands/run-unit-tests.sh +++ b/.buildkite/commands/run-unit-tests.sh @@ -15,11 +15,11 @@ TESTS_EXIT_STATUS=$? set -e if [[ "$TESTS_EXIT_STATUS" -eq 0 ]]; then - echo -e "--- ⚒️ Uploading code coverage" + echo -e "\n--- ⚒️ Uploading code coverage" .buildkite/commands/upload-code-coverage.sh fi -echo -e "--- 🚦 Report Tests Status" +echo -e "\n--- 🚦 Report Tests Status" results_file="WooCommerce/build/test-results/merged-test-results.xml" # Merge JUnit results into a single file (for performance reasons with reporting) # See https://github.com/woocommerce/woocommerce-android/pull/12064 From 4211e37dc37742a6c59d3da7f22a9c62f921f740 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Wed, 30 Apr 2025 13:28:10 +0200 Subject: [PATCH 17/18] 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 18/18] 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) - - }