🚀 WooCommerce offers a robust platform for e-commerce businesses, but it requires some customizations to optimize user experience. Inventory management plays a **critical role** in the operational efficiency of businesses. Thanks to WooCommerce’s flexible structure, it’s possible to make inventory control more intuitive and user-friendly. 📈
📦 My purpose in developing this code is to simplify inventory management, allowing users to quickly and directly update the stock status and quantities of products. This eliminates the need to navigate to product edit pages, enabling stock updates right from the product listing screen. This customization helps businesses manage their daily operations more efficiently, saves time, and enhances customer satisfaction. ✅
🔧 In this article, we will examine a piece of code that enhances WooCommerce inventory management. This code adds a dedicated stock column in product lists, provides user-friendly modal windows for swift stock updates on product pages, and implements real-time stock control and updates with AJAX. These improvements enrich the user experience of your e-commerce sites while providing business owners with flexibility and speed in their daily workflows. 💡
Features of the Code:
- Stock Management in Admin Bar: Adds a new menu called “Stock Management” to the admin bar when on a product page. Clicking this menu opens a modal window that can be used to update stock status and quantity.
- Quick Stock Status Update: The modal window includes a dropdown menu to select the product’s stock status (In Stock, Out of Stock, On Backorder). The stock status is instantly updated with the selection made from this menu.
- Easy Stock Quantity Update: When the stock status is set to “In Stock”, a number field is displayed to update the stock quantity.
- Real-Time Updates: Using AJAX technology, all stock updates are made in real-time without refreshing the page.
- Custom Stock Column in Product List: This code creates a custom column called “Stock Status” in the product list and displays each product’s stock status and quantity in this column. This column also has the ability to be sorted by stock status.
- Quick Stock Status Toggle: Administrators can quickly switch between “In Stock” and “Out of Stock” by clicking on the status in the “Stock Status” column in the product list.
- Direct Quantity Editing: Administrators can directly edit by clicking on the quantity value in the product list.
Screenshots:
1. Stock Management Menu in Admin Bar:
2. Stock Management Modal Window:
3. Custom Stock Column in Product List:
Code:
<?php
/**
* WooLogger Stock Management
*
* This code enhances WooCommerce stock management by adding a custom column
* to the product list and a modal for quick stock updates on product pages.
* It provides AJAX functionality for real-time stock status and quantity updates.
*
* Created by: woologger.com
* Version: 1.0
*/
// Add Stock Management to Admin Bar
add_action('admin_bar_menu', 'woologger_custom_admin_bar_stock_management', 100);
function woologger_custom_admin_bar_stock_management($admin_bar) {
if (!is_admin() && is_product() && current_user_can('edit_products')) {
$admin_bar->add_menu(array(
'id' => 'woologger-stock-management',
'title' => '<span class="ab-icon dashicons dashicons-chart-area"></span> Stock Management',
'href' => '#',
'meta' => array(
'onclick' => 'woologgerOpenStockManagement(); return false;',
),
));
}
}
// Handle Stock Status Update
add_action('wp_ajax_woologger_update_stock_status', 'woologger_handle_update_stock_status');
function woologger_handle_update_stock_status() {
check_ajax_referer('woologger_update_stock_status', 'security');
$product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
$new_status = isset($_POST['stock_status']) ? sanitize_text_field($_POST['stock_status']) : '';
$new_quantity = isset($_POST['stock_quantity']) ? intval($_POST['stock_quantity']) : 0;
$product = wc_get_product($product_id);
if ($product) {
$product->set_stock_status($new_status);
if ($product->managing_stock() && $new_status === 'instock') {
$product->set_stock_quantity($new_quantity);
} elseif ($product->managing_stock()) {
$product->set_stock_quantity(0);
}
$product->save();
wc_delete_product_transients($product_id);
wp_send_json_success(array('message' => 'Stock status updated successfully.'));
} else {
wp_send_json_error(array('message' => 'Product not found.'));
}
wp_die();
}
// Add Stock Management Modal
add_action('wp_footer', 'woologger_add_stock_management_modal');
function woologger_add_stock_management_modal() {
if (is_product() && current_user_can('edit_products')) {
global $product;
?>
<div id="woologger-stock-management-modal" class="woologger-modal">
<div class="woologger-modal-content">
<span class="woologger-close">×</span>
<h2>Stock Management</h2>
<div id="woologger-stock-management-form">
<input type="hidden" id="woologger-product-id" value="<?php echo esc_attr($product->get_id()); ?>">
<div class="woologger-form-group">
<label for="woologger-stock-status">Stock Status:</label>
<select id="woologger-stock-status">
<option value="instock" <?php selected($product->get_stock_status(), 'instock'); ?>>In Stock</option>
<option value="outofstock" <?php selected($product->get_stock_status(), 'outofstock'); ?>>Out of Stock</option>
<option value="onbackorder" <?php selected($product->get_stock_status(), 'onbackorder'); ?>>On Backorder</option>
</select>
</div>
<?php if ($product->managing_stock()): ?>
<div class="woologger-form-group" id="woologger-quantity-group" style="display: <?php echo $product->get_stock_status() === 'instock' ? 'block' : 'none'; ?>">
<label for="woologger-stock-quantity">Stock Quantity:</label>
<input type="number" id="woologger-stock-quantity" value="<?php echo esc_attr($product->get_stock_quantity()); ?>" min="0">
</div>
<?php endif; ?>
<div class="woologger-button-group">
<button id="woologger-update-button" onclick="woologgerUpdateStockStatus()">Update</button>
<button id="woologger-cancel-button" onclick="woologgerCloseStockManagement()">Cancel</button>
</div>
</div>
<div id="woologger-update-message"></div>
</div>
</div>
<style>
.woologger-modal {
display: none;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.woologger-modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 30px;
border: none;
width: 90%;
max-width: 500px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.woologger-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
transition: 0.3s;
}
.woologger-close:hover,
.woologger-close:focus {
color: #000;
}
.woologger-modal h2 {
margin-top: 0;
color: #333;
font-size: 24px;
margin-bottom: 20px;
}
.woologger-form-group {
margin-bottom: 20px;
}
.woologger-form-group label {
display: block;
margin-bottom: 8px;
color: #555;
font-weight: bold;
}
.woologger-form-group select,
.woologger-form-group input[type="number"] {
width: 100%;
border: 1px solid #ddd;
border-radius: 6px;
box-sizing: border-box;
font-size: 16px;
transition: border-color 0.3s;
height: 45px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E');
background-repeat: no-repeat;
background-position: right 12px top 50%;
background-size: 12px auto;
padding-right: 30px;
}
.woologger-form-group select:focus,
.woologger-form-group input[type="number"]:focus {
outline: none;
border-color: #4CAF50;
}
.woologger-button-group {
text-align: right;
margin-top: 30px;
}
.woologger-button-group button {
padding: 12px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 16px;
transition: background-color 0.3s;
}
#woologger-update-button {
background-color: #4CAF50;
color: white;
margin-right: 10px;
}
#woologger-update-button:hover {
background-color: #45a049;
}
#woologger-cancel-button {
background-color: #f44336;
color: white;
}
#woologger-cancel-button:hover {
background-color: #da190b;
}
#woologger-update-message {
margin-top: 20px;
padding: 15px;
border-radius: 6px;
display: none;
font-size: 16px;
text-align: center;
}
@media (max-width: 600px) {
.woologger-modal-content {
width: 95%;
padding: 20px;
}
.woologger-button-group button {
width: 100%;
margin-bottom: 10px;
}
#woologger-update-button {
margin-right: 0;
}
}
</style>
<script>
function woologgerOpenStockManagement() {
document.getElementById('woologger-stock-management-modal').style.display = 'block';
woologgerToggleQuantityField();
}
function woologgerCloseStockManagement() {
document.getElementById('woologger-stock-management-modal').style.display = 'none';
}
function woologgerToggleQuantityField() {
var stockStatus = document.getElementById('woologger-stock-status').value;
var quantityGroup = document.getElementById('woologger-quantity-group');
if (quantityGroup) {
quantityGroup.style.display = (stockStatus === 'instock') ? 'block' : 'none';
}
}
function woologgerUpdateStockStatus() {
var productId = document.getElementById('woologger-product-id').value;
var stockStatus = document.getElementById('woologger-stock-status').value;
var stockQuantity = document.getElementById('woologger-stock-quantity') ? document.getElementById('woologger-stock-quantity').value : 0;
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'woologger_update_stock_status',
security: '<?php echo wp_create_nonce('woologger_update_stock_status'); ?>',
product_id: productId,
stock_status: stockStatus,
stock_quantity: stockQuantity
},
success: function(response) {
if (response.success) {
location.reload();
} else {
var messageElement = document.getElementById('woologger-update-message');
messageElement.innerHTML = response.data.message;
messageElement.style.display = 'block';
messageElement.style.backgroundColor = '#f2dede';
messageElement.style.color = '#a94442';
}
}
});
}
document.getElementById('woologger-stock-status').addEventListener('change', woologgerToggleQuantityField);
window.onclick = function(event) {
if (event.target == document.getElementById('woologger-stock-management-modal')) {
woologgerCloseStockManagement();
}
}
document.querySelector('.woologger-close').onclick = woologgerCloseStockManagement;
</script>
<?php
}
}
// Load Custom CSS Styles for Stock Management Interface
add_action('admin_head', 'woologger_add_custom_stock_styles');
function woologger_add_custom_stock_styles(): void {
$screen = get_current_screen();
if ($screen?->id !== 'edit-product') return;
?>
<style>
.wl-stock-info {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 5px;
padding: 5px;
border-radius: 4px;
background-color: #f9f9f9;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
transition: all 0.3s ease;
}
.wl-stock-info:hover { background-color: #f0f0f0; }
.wl-stock-status, .wl-stock-quantity {
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.9em;
text-align: center;
transition: all 0.3s ease;
}
.wl-stock-status:hover, .wl-stock-quantity:hover { opacity: 0.8; }
.wl-stock-status {
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.wl-stock-status.in-stock {
background-color: #4CAF50;
color: white;
}
.wl-stock-status.out-of-stock {
background-color: #F44336;
color: white;
}
.wl-stock-quantity {
background-color: #e9e9e9;
color: #333;
}
.wl-stock-quantity-input {
width: 100%;
text-align: center;
padding: 4px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 0.9em;
}
.wl-updating {
opacity: 0.5;
pointer-events: none;
}
@keyframes wl-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.wl-updating::after {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #ccc;
border-top: 2px solid #333;
border-radius: 50%;
animation: wl-spin 1s linear infinite;
margin-left: 10px;
}
</style>
<?php
}
// Add Custom Stock Column to Product List
add_filter('manage_edit-product_columns', 'woologger_custom_product_column', 20);
function woologger_custom_product_column(array $columns): array {
unset($columns['is_in_stock']);
$columns['woologger_stock'] = __('Stock Status', 'woocommerce');
return $columns;
}
// Add Content to Custom Stock Column
add_action('manage_product_posts_custom_column', 'woologger_custom_product_list_column_content', 20, 2);
function woologger_custom_product_list_column_content(string $column, int $product_id): void {
if ($column != 'woologger_stock') return;
$product = wc_get_product($product_id);
$stock_status = $product->get_stock_status();
$stock_quantity = $product->get_stock_quantity();
$manage_stock = $product->get_manage_stock();
$class = $stock_status == 'instock' ? 'in-stock' : 'out-of-stock';
$text = $stock_status == 'instock' ? __('In Stock', 'woocommerce') : __('Out of Stock', 'woocommerce');
echo '<div class="wl-stock-info">';
echo '<span class="wl-stock-status ' . esc_attr($class) . '" data-product-id="' . esc_attr($product_id) . '">' .
esc_html($text) . '</span>';
if ($manage_stock && $stock_quantity !== null) {
echo '<span class="wl-stock-quantity" data-product-id="' . esc_attr($product_id) . '">' . esc_html($stock_quantity) . ' items</span>';
}
echo '</div>';
}
// Ajax Handler for Toggling Stock Status
add_action('wp_ajax_woologger_toggle_stock_status', 'woologger_toggle_stock_status');
function woologger_toggle_stock_status(): void {
check_ajax_referer('woologger_stock_nonce', 'security');
if (!current_user_can('edit_products')) {
wp_send_json_error('Permission denied');
}
$product_id = $_POST['product_id'] ?? 0;
$product = wc_get_product($product_id);
if (!$product) {
wp_send_json_error('Product not found');
}
$current_status = $product->get_stock_status();
$new_status = $current_status === 'instock' ? 'outofstock' : 'instock';
$manage_stock = $product->get_manage_stock();
if ($manage_stock) {
if ($new_status == 'outofstock') {
$product->set_stock_quantity(0);
} else {
$previous_quantity = get_post_meta($product_id, '_previous_stock_quantity', true);
$product->set_stock_quantity($previous_quantity ? (int)$previous_quantity : 1);
}
}
$product->set_stock_status($new_status);
$product->save();
update_post_meta($product_id, '_previous_stock_quantity', $product->get_stock_quantity());
$updated_product = wc_get_product($product_id);
if ($updated_product->get_stock_status() !== $new_status) {
wp_send_json_error('Failed to update stock status');
}
wp_send_json_success([
'status' => $new_status,
'text' => $new_status === 'instock' ? __('In Stock', 'woocommerce') : __('Out of Stock', 'woocommerce'),
'quantity' => $manage_stock ? $updated_product->get_stock_quantity() : null
]);
}
// Ajax Handler for Updating Stock Quantity
add_action('wp_ajax_woologger_update_stock_quantity', 'woologger_update_stock_quantity');
function woologger_update_stock_quantity(): void {
check_ajax_referer('woologger_stock_nonce', 'security');
if (!current_user_can('edit_products')) {
wp_send_json_error('Permission denied');
}
$product_id = $_POST['product_id'] ?? 0;
$new_quantity = $_POST['quantity'] ?? 0;
$product = wc_get_product($product_id);
if (!$product) {
wp_send_json_error('Product not found');
}
$product->set_stock_quantity((int)$new_quantity);
$product->set_stock_status($new_quantity > 0 ? 'instock' : 'outofstock');
$product->save();
update_post_meta($product_id, '_previous_stock_quantity', $new_quantity);
$updated_product = wc_get_product($product_id);
$new_status = $updated_product->get_stock_status();
wp_send_json_success([
'status' => $new_status,
'text' => $new_status === 'instock' ? __('In Stock', 'woocommerce') : __('Out of Stock', 'woocommerce'),
'quantity' => $updated_product->get_stock_quantity()
]);
}
// Add JavaScript for Handling Stock Management Interactions
add_action('admin_footer', 'woologger_add_custom_stock_toggle_script');
function woologger_add_custom_stock_toggle_script(): void {
if (!function_exists('WC')) return;
$screen = get_current_screen();
if ($screen?->id !== 'edit-product') return;
?>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
// Helper function for AJAX requests
function ajaxRequest(url, method, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
callback(JSON.parse(xhr.responseText));
} else {
alert('AJAX error: ' + xhr.status);
}
}
};
xhr.send(encodeURI(Object.keys(data).map(key => key + '=' + data[key]).join('&')));
}
// Toggle stock status
document.querySelectorAll('.wl-stock-status').forEach(function(statusElement) {
statusElement.addEventListener('click', function() {
var stockInfo = this.closest('.wl-stock-info');
var productId = this.dataset.productId;
stockInfo.classList.add('wl-updating');
ajaxRequest(ajaxurl, 'POST', {
action: 'woologger_toggle_stock_status',
product_id: productId,
security: '<?php echo wp_create_nonce('woologger_stock_nonce'); ?>'
}, function(response) {
if (response.success) {
statusElement.textContent = response.data.text;
statusElement.classList.remove('in-stock', 'out-of-stock');
statusElement.classList.add(response.data.status === 'instock' ? 'in-stock' : 'out-of-stock');
if (response.data.quantity !== null) {
var quantitySpan = stockInfo.querySelector('.wl-stock-quantity');
if (quantitySpan) {
quantitySpan.textContent = response.data.quantity + ' items';
} else {
var newQuantitySpan = document.createElement('span');
newQuantitySpan.className = 'wl-stock-quantity';
newQuantitySpan.dataset.productId = productId;
newQuantitySpan.textContent = response.data.quantity + ' items';
stockInfo.appendChild(newQuantitySpan);
}
}
} else {
alert('Error: ' + response.data);
}
stockInfo.classList.remove('wl-updating');
});
});
});
// Update stock quantity
document.addEventListener('click', function(event) {
if (event.target.classList.contains('wl-stock-quantity')) {
var quantityElement = event.target;
var productId = quantityElement.dataset.productId;
var currentQuantity = parseInt(quantityElement.textContent, 10);
var input = document.createElement('input');
input.type = 'number';
input.className = 'wl-stock-quantity-input';
input.value = currentQuantity;
quantityElement.style.display = 'none';
quantityElement.parentNode.insertBefore(input, quantityElement.nextSibling);
input.focus();
input.addEventListener('blur', function() {
var newQuantity = parseInt(input.value, 10);
if (isNaN(newQuantity) || newQuantity < 0) {
input.remove();
quantityElement.style.display = '';
return;
}
var stockInfo = quantityElement.closest('.wl-stock-info');
stockInfo.classList.add('wl-updating');
ajaxRequest(ajaxurl, 'POST', {
action: 'woologger_update_stock_quantity',
product_id: productId,
quantity: newQuantity,
security: '<?php echo wp_create_nonce('woologger_stock_nonce'); ?>'
}, function(response) {
if (response.success) {
quantityElement.textContent = response.data.quantity + ' items';
var statusSpan = stockInfo.querySelector('.wl-stock-status');
statusSpan.textContent = response.data.text;
statusSpan.classList.remove('in-stock', 'out-of-stock');
statusSpan.classList.add(response.data.status === 'instock' ? 'in-stock' : 'out-of-stock');
} else {
alert('Error: ' + response.data);
}
input.remove();
quantityElement.style.display = '';
stockInfo.classList.remove('wl-updating');
});
});
}
});
});
</script>
<?php
}
// Make the Custom Column Sortable
add_filter('manage_edit-product_sortable_columns', 'woologger_custom_product_sortable_columns');
function woologger_custom_product_sortable_columns(array $columns): array {
$columns['woologger_stock'] = 'woologger_stock';
return $columns;
}
// Handle Sorting of the Custom Column
add_action('pre_get_posts', 'woologger_custom_product_orderby');
function woologger_custom_product_orderby($query): void {
if (!is_admin() || !$query->is_main_query() || $query->get('post_type') !== 'product') return;
$orderby = $query->get('orderby');
if ('woologger_stock' == $orderby) {
$query->set('meta_key', '_stock_status');
$query->set('orderby', 'meta_value');
}
}
JavaScriptAdding Code to WordPress functions.php File
The functions.php file is a very useful tool for customizing your WordPress site’s functionality. However, it’s important to be careful when adding code to this file; because an incorrect operation can cause your site to crash completely.
Fortunately, plugins like WPCode make the code adding process much easier and safer. With WPCode, you can manage code snippets without directly interfering with the functions.php file. This minimizes the risk and ensures your site runs stably.
Woologger.com offers a great step-by-step guide on how to safely add code to the functions.php file. In this guide, you can learn how to avoid potential risks of adding code using traditional methods and how plugins like WPCode simplify this process. You can also learn how to back up your site before applying changes.
Check out the guide on Woologger.com for detailed information on adding code to the functions.php file and more information on using WPCode.