feat: optionally create missing WooCommerce products from BC items
The plugin only ever updated existing WooCommerce products - nothing created new ones, so a category-filtered sync would just skip every BC item without a matching product, no matter how the filter was configured. Adds an opt-in "Create Missing Products" setting (default off, so existing update-only behavior is unchanged unless explicitly enabled) that creates a bare WC_Product_Simple (name, SKU from GTIN/number, configurable draft/publish status) for unmatched BC items in the category-filtered sync path, then runs it straight through the existing update_product() call for stock/price/brand - no duplicated logic. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,12 @@ class WBC_Admin {
|
|||||||
register_setting( 'wbc_sync', 'wbc_category_parent_map', array(
|
register_setting( 'wbc_sync', 'wbc_category_parent_map', array(
|
||||||
'sanitize_callback' => 'sanitize_textarea_field',
|
'sanitize_callback' => 'sanitize_textarea_field',
|
||||||
) );
|
) );
|
||||||
|
register_setting( 'wbc_sync', 'wbc_create_missing_products', array(
|
||||||
|
'sanitize_callback' => array( $this, 'sanitize_checkbox' ),
|
||||||
|
) );
|
||||||
|
register_setting( 'wbc_sync', 'wbc_new_product_status', array(
|
||||||
|
'sanitize_callback' => array( $this, 'sanitize_new_product_status' ),
|
||||||
|
) );
|
||||||
|
|
||||||
// Order settings (own group)
|
// Order settings (own group)
|
||||||
register_setting( 'wbc_orders', 'wbc_enable_order_sync', array(
|
register_setting( 'wbc_orders', 'wbc_enable_order_sync', array(
|
||||||
@@ -173,6 +179,16 @@ class WBC_Admin {
|
|||||||
return $value === 'yes' ? 'yes' : 'no';
|
return $value === 'yes' ? 'yes' : 'no';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize the new-product status setting
|
||||||
|
*
|
||||||
|
* @param string $value Input value.
|
||||||
|
* @return string 'publish' or 'draft'.
|
||||||
|
*/
|
||||||
|
public function sanitize_new_product_status( $value ) {
|
||||||
|
return $value === 'publish' ? 'publish' : 'draft';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueue admin scripts
|
* Enqueue admin scripts
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -225,6 +225,40 @@ $tabs = array(
|
|||||||
<p class="description">
|
<p class="description">
|
||||||
<?php esc_html_e( 'Optional. Comma-separated BC item category codes. Only products whose BC item category is in this list will be synced. Leave empty to sync all categories.', 'woo-business-central' ); ?>
|
<?php esc_html_e( 'Optional. Comma-separated BC item category codes. Only products whose BC item category is in this list will be synced. Leave empty to sync all categories.', 'woo-business-central' ); ?>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'When set, sync fetches items from BC filtered by category directly, instead of checking every WooCommerce product.', 'woo-business-central' ); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php esc_html_e( 'Create Missing Products', 'woo-business-central' ); ?></th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="wbc_create_missing_products" value="yes"
|
||||||
|
<?php checked( get_option( 'wbc_create_missing_products', 'no' ), 'yes' ); ?> />
|
||||||
|
<?php esc_html_e( 'Create a new WooCommerce product when a BC item in an allowed category has no matching product', 'woo-business-central' ); ?>
|
||||||
|
</label>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Only applies with a Category Filter set (BC-first mode). Without a filter, sync only updates existing products and never creates new ones.', 'woo-business-central' ); ?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">
|
||||||
|
<label for="wbc_new_product_status"><?php esc_html_e( 'New Product Status', 'woo-business-central' ); ?></label>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<select id="wbc_new_product_status" name="wbc_new_product_status">
|
||||||
|
<option value="draft" <?php selected( get_option( 'wbc_new_product_status', 'draft' ), 'draft' ); ?>>
|
||||||
|
<?php esc_html_e( 'Draft (review before publishing)', 'woo-business-central' ); ?>
|
||||||
|
</option>
|
||||||
|
<option value="publish" <?php selected( get_option( 'wbc_new_product_status' ), 'publish' ); ?>>
|
||||||
|
<?php esc_html_e( 'Publish immediately', 'woo-business-central' ); ?>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Status assigned to products created by "Create Missing Products".', 'woo-business-central' ); ?>
|
||||||
|
</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -285,6 +285,12 @@ class WBC_Product_Sync {
|
|||||||
$this->results['total']++;
|
$this->results['total']++;
|
||||||
|
|
||||||
$product = $this->find_wc_product_for_item( $item );
|
$product = $this->find_wc_product_for_item( $item );
|
||||||
|
$is_new = false;
|
||||||
|
|
||||||
|
if ( ! $product && get_option( 'wbc_create_missing_products', 'no' ) === 'yes' ) {
|
||||||
|
$product = $this->create_wc_product_from_item( $item );
|
||||||
|
$is_new = (bool) $product;
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! $product ) {
|
if ( ! $product ) {
|
||||||
$this->results['skipped']++;
|
$this->results['skipped']++;
|
||||||
@@ -308,11 +314,54 @@ class WBC_Product_Sync {
|
|||||||
|
|
||||||
if ( $updated ) {
|
if ( $updated ) {
|
||||||
$this->results['success']++;
|
$this->results['success']++;
|
||||||
|
if ( $is_new ) {
|
||||||
|
WBC_Logger::info( 'ProductSync', 'Created new WooCommerce product from BC item', array(
|
||||||
|
'product_id' => $product->get_id(),
|
||||||
|
'item_number' => $item['number'] ?? '',
|
||||||
|
'sku' => $product->get_sku(),
|
||||||
|
) );
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->results['failed']++;
|
$this->results['failed']++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WooCommerce product from a BC item
|
||||||
|
*
|
||||||
|
* Bare creation only (name, SKU, status) - stock/price/brand are filled
|
||||||
|
* in right after by the normal update_product() call, so that logic
|
||||||
|
* isn't duplicated here.
|
||||||
|
*
|
||||||
|
* @param array $item BC item data.
|
||||||
|
* @return WC_Product|false
|
||||||
|
*/
|
||||||
|
private function create_wc_product_from_item( $item ) {
|
||||||
|
$sku = ! empty( $item['gtin'] ) ? $item['gtin'] : ( $item['number'] ?? '' );
|
||||||
|
|
||||||
|
if ( empty( $sku ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product = new WC_Product_Simple();
|
||||||
|
$product->set_name( $item['displayName'] ?? $sku );
|
||||||
|
|
||||||
|
try {
|
||||||
|
$product->set_sku( $sku );
|
||||||
|
$product->set_status( get_option( 'wbc_new_product_status', 'draft' ) === 'publish' ? 'publish' : 'draft' );
|
||||||
|
$product->save();
|
||||||
|
} catch ( Exception $e ) {
|
||||||
|
WBC_Logger::error( 'ProductSync', 'Failed to create WooCommerce product from BC item', array(
|
||||||
|
'item_number' => $item['number'] ?? '',
|
||||||
|
'sku' => $sku,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $product;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the WooCommerce product matching a BC item, via a local SKU lookup
|
* Find the WooCommerce product matching a BC item, via a local SKU lookup
|
||||||
* (no BC round trip) - tries GTIN, then item number, then Item Reference
|
* (no BC round trip) - tries GTIN, then item number, then Item Reference
|
||||||
|
|||||||
Reference in New Issue
Block a user