diff --git a/includes/Handlers/Category.php b/includes/Handlers/Category.php index 2387cf679..422db2e4c 100644 --- a/includes/Handlers/Category.php +++ b/includes/Handlers/Category.php @@ -263,4 +263,56 @@ public static function get_square_category_id( $catalog_item ) { return $catalog_category_id; } + + /** + * Get the WooCommerce category IDs from a catalog item. + * + * @since x.x.x + * + * @param \Square\Models\CatalogItem $catalog_item the catalog item object + * @return array + */ + public static function get_category_ids_from_catalog_item( $catalog_item ) { + $category_ids = array(); + $missing_category_ids = array(); + + if ( empty( $catalog_item ) || ! $catalog_item instanceof \Square\Models\CatalogItem ) { + return $category_ids; + } + + if ( $catalog_item->getCategories() && is_array( $catalog_item->getCategories() ) ) { + foreach ( $catalog_item->getCategories() as $category ) { + if ( $category instanceof \Square\Models\CatalogObjectCategory ) { + $category_id = self::get_category_id_by_square_id( $category->getId() ); + if ( $category_id ) { + $category_ids[] = $category_id; + } else { + $missing_category_ids[] = $category->getId(); + } + } + } + } + + // Fetch and import missing categories. + if ( ! empty( $missing_category_ids ) ) { + try { + $response = wc_square()->get_api()->batch_retrieve_catalog_objects( $missing_category_ids ); + if ( $response->get_data() instanceof \Square\Models\BatchRetrieveCatalogObjectsResponse ) { + $missing_categories = $response->get_data()->getObjects(); + if ( $missing_categories && is_array( $missing_categories ) ) { + foreach ( $missing_categories as $missing_category ) { + $imported_category_id = self::import_or_update( $missing_category ); + if ( $imported_category_id ) { + $category_ids[] = $imported_category_id; + } + } + } + } + } catch ( \Exception $e ) { + wc_square()->log( 'Error fetching missing categories for product ' . $catalog_item->getName() . ': ' . $e->getMessage() ); + } + } + + return array_unique( $category_ids ); + } } diff --git a/includes/Handlers/Product.php b/includes/Handlers/Product.php index 4c0560aae..a8cb23940 100644 --- a/includes/Handlers/Product.php +++ b/includes/Handlers/Product.php @@ -230,35 +230,6 @@ public static function update_from_square( \WC_Product $product, \Square\Models\ $product->set_description( $product_description ); } - $square_category_id = Category::get_square_category_id( $catalog_item ); - $category_id = Category::get_category_id_by_square_id( $square_category_id ); - - if ( $category_id ) { - wp_set_object_terms( $product->get_id(), intval( $category_id ), 'product_cat' ); - } else { - $message = sprintf( - /* translators: Placeholder: %s category ID */ - __( 'Square category with id (%s) was not imported to your Store. Please run Import Products from Square settings.', 'woocommerce-square' ), - $square_category_id - ); - - $records = Records::get_records(); - foreach ( $records as $record ) { - if ( $record->get_message() === $message ) { - $is_recorded = true; - } - } - - if ( ! isset( $is_recorded ) ) { - Records::set_record( - array( - 'type' => 'alert', - 'message' => $message, - ) - ); - } - } - if ( $catalog_id ) { $product->update_meta_data( self::SQUARE_ID_META_KEY, $catalog_id ); } diff --git a/includes/Sync/Product_Import.php b/includes/Sync/Product_Import.php index c1aac7934..1228a6c51 100644 --- a/includes/Sync/Product_Import.php +++ b/includes/Sync/Product_Import.php @@ -613,44 +613,7 @@ public function extract_product_data( $catalog_object, $product = null ) { ); // Process multiple categories from Square. - $item_data = $catalog_object->getItemData(); - $categories = array(); - $missing_category_ids = array(); - - if ( $item_data->getCategories() && is_array( $item_data->getCategories() ) ) { - foreach ( $item_data->getCategories() as $category ) { - if ( $category instanceof \Square\Models\CatalogObjectCategory ) { - $category_id = Category::get_category_id_by_square_id( $category->getId() ); - if ( $category_id ) { - $categories[] = $category_id; - } else { - $missing_category_ids[] = $category->getId(); - } - } - } - } - - // Fetch and import missing categories. - if ( ! empty( $missing_category_ids ) ) { - try { - $response = wc_square()->get_api()->batch_retrieve_catalog_objects( $missing_category_ids ); - if ( $response->get_data() instanceof \Square\Models\BatchRetrieveCatalogObjectsResponse ) { - $missing_categories = $response->get_data()->getObjects(); - if ( $missing_categories && is_array( $missing_categories ) ) { - foreach ( $missing_categories as $missing_category ) { - $imported_category_id = Category::import_or_update( $missing_category ); - if ( $imported_category_id ) { - $categories[] = $imported_category_id; - } - } - } - } - } catch ( \Exception $e ) { - wc_square()->log( 'Error fetching missing categories for product ' . $product_name . ': ' . $e->getMessage() ); - } - } - - $data['categories'] = array_unique( $categories ); + $data['categories'] = Category::get_category_ids_from_catalog_item( $catalog_object->getItemData() ); // variable product if ( 'variable' === $data['type'] ) { diff --git a/tests/e2e/specs/c2.woo-sor.spec.js b/tests/e2e/specs/c2.woo-sor.spec.js index c44d91727..6a54a67a6 100644 --- a/tests/e2e/specs/c2.woo-sor.spec.js +++ b/tests/e2e/specs/c2.woo-sor.spec.js @@ -31,7 +31,7 @@ test.beforeAll( 'Setup', async ( { baseURL } ) => { page, { name: 'iPhone Pro', content: 'iPhone Pro content', - category: 'Mobiles', + category: 'Mobiles, Phones', regularPrice: '499', sku: 'iphone', }, @@ -70,6 +70,7 @@ test( 'iPhone Pro pushed to Square @sync', async ( { page } ) => { variations, description, category, + categories: categoriesIds, } = extractCatalogInfo( result.objects[0] ); expect( name ).toEqual( 'iPhone Pro' ); @@ -78,15 +79,21 @@ test( 'iPhone Pro pushed to Square @sync', async ( { page } ) => { expect(description).toEqual('iPhone Pro content'); let categoryName = ''; + let categoriesNames = []; if (category) { const categories = await listCategories(); if (categories.objects) { categoryName = categories.objects .filter((cat) => cat.id === category) .map((cat) => cat.category_data.name)[0]; + categoriesNames = categories.objects + .filter((cat) => categoriesIds.includes(cat.id)) + .map((cat) => cat.category_data.name); } } expect(categoryName).toEqual('Mobiles'); + expect(categoriesNames).toContain('Mobiles'); + expect(categoriesNames).toContain('Phones'); const inventory = await retrieveInventoryCount( variations[ 0 ].id ); diff --git a/tests/e2e/specs/c4.product-import.spec.js b/tests/e2e/specs/c4.product-import.spec.js index 2300b75d1..0a5b83521 100644 --- a/tests/e2e/specs/c4.product-import.spec.js +++ b/tests/e2e/specs/c4.product-import.spec.js @@ -24,9 +24,18 @@ test.beforeAll( 'Setup', async () => { await deleteAllProducts( page ); await deleteAllCatalogItems(); - const categoryResponse = await createCategory('Hats'); + const categoryResponse = await createCategory( 'Hats' ); const categoryId = categoryResponse.catalog_object?.id || ''; - const response = await createCatalogObject( 'Cap', 'cap', 1350, 'This is a very good cap, no cap.', categoryId ); + const categoryResponse2 = await createCategory( 'Hats2' ); + const categoryId2 = categoryResponse2.catalog_object?.id || ''; + const response = await createCatalogObject( + 'Cap', + 'cap', + 1350, + 'This is a very good cap, no cap.', + categoryId, + categoryId2 + ); itemId = response.catalog_object.item_data.variations[0].id; await updateCatalogItemInventory( itemId, '53' ); @@ -70,7 +79,7 @@ test( 'Import Cap from Square @sync', async ( { page, baseURL } ) => { await expect( await page.locator( '.entry-summary .sku_wrapper' ) ).toHaveText( 'SKU: cap-regular' ); await expect( await page.getByText( 'This is a very good cap, no cap.' ) ).toBeVisible(); await expect( await page.getByText( '53 in stock' ) ).toBeVisible(); - await expect( await page.getByText( 'Category: Hats' ) ).toBeVisible(); + await expect( await page.getByText( 'Categories: Hats, Hats2' ) ).toBeVisible(); } ); test('Sync Inventory stock from Square on the product edit screen - (SOR Square) @sync', async ({ diff --git a/tests/e2e/utils/helper.js b/tests/e2e/utils/helper.js index e6e4e2bfb..fecce4c7f 100644 --- a/tests/e2e/utils/helper.js +++ b/tests/e2e/utils/helper.js @@ -112,9 +112,27 @@ export async function createProduct( page, product, save = true, newEditor = fal if ( product.category ) { await page.locator('#product_cat-add-toggle').click(); - await page.locator('#newproduct_cat').fill( product.category ); - await page.locator('#product_cat-add-submit').click(); - await page.waitForTimeout( 2000 ); + const categories = product.category + .split( ',' ) + .map( ( c ) => c.trim() ) + .filter( Boolean ); + for ( const category of categories ) { + if ( + await page + .locator( '#product_catchecklist' ) + .getByText( category ) + .isVisible() + ) { + await page + .locator( '#product_catchecklist' ) + .getByText( category ) + .click(); + } else { + await page.locator('#newproduct_cat').fill( category ); + await page.locator('#product_cat-add-submit').click(); + await page.waitForTimeout( 2000 ); + } + } } if ( save ) { diff --git a/tests/e2e/utils/square-sandbox.js b/tests/e2e/utils/square-sandbox.js index 5d4e0fcc5..366012597 100644 --- a/tests/e2e/utils/square-sandbox.js +++ b/tests/e2e/utils/square-sandbox.js @@ -132,6 +132,11 @@ export function extractCatalogInfo( catalogObject = {} ) { category = catalogObject.categories[ 0 ]?.id; } + const categories = + ( catalogObject.item_data?.categories || [] ) + .map( ( cat ) => cat?.id || '' ) + .filter( ( cat ) => cat !== '' ); + const variations = catalogObject.item_data.variations.map( ( variation ) => { return { @@ -149,6 +154,7 @@ export function extractCatalogInfo( catalogObject = {} ) { catalogId, name, category, + categories, description, variations, }; @@ -159,14 +165,18 @@ export function extractCatalogInfo( catalogObject = {} ) { * @param {string} name Name of the variation. * @param {string} sku SKU. * @param {string} price Price of the variation. - * @return {Object} + * @param {string} description Description of the variation. + * @param {string} categoryId Category ID. + * @param {string} categoryId2 Category ID 2. + * @return {Object} Response object. */ export async function createCatalogObject( name, sku, price, description = '', - categoryId = '' + categoryId = '', + categoryId2 = '' ) { const url = 'https://connect.squareupsandbox.com/v2/catalog/object'; const method = 'POST'; @@ -204,7 +214,11 @@ export async function createCatalogObject( }; if ( categoryId ) { - data.object.item_data.categories = [ { id: categoryId } ]; + const categories = [ { id: categoryId } ]; + if ( categoryId2 ) { + categories.push( { id: categoryId2 } ); + } + data.object.item_data.categories = categories; data.object.item_data.reporting_category = { id: categoryId }; }