Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8146457ea | |||
| 3c442eb28b | |||
| 1b2ff44eb4 | |||
| 5ba5c9f6a4 |
41
assets/css/order-admin.css
Normal file
41
assets/css/order-admin.css
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
.siti-stock-order-item-sources {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 4px 0 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.siti-stock-order-item-sources__label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1d2327;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.siti-stock-order-item-sources__badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #1d2327;
|
||||||
|
}
|
||||||
|
|
||||||
|
.siti-stock-order-item-sources__badge .dashicons {
|
||||||
|
font-size: 13px;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
line-height: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.siti-stock-order-item-sources__badge.is-local {
|
||||||
|
background: #e6f8ef;
|
||||||
|
color: #17633d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.siti-stock-order-item-sources__badge.is-external {
|
||||||
|
background: #f1ecff;
|
||||||
|
color: #3e2a85;
|
||||||
|
}
|
||||||
@@ -14,11 +14,17 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
*/
|
*/
|
||||||
private $external_stock_key;
|
private $external_stock_key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $order_item_stock_source_key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $meta_key Meta key used to store external stock.
|
* @param string $meta_key Meta key used to store external stock.
|
||||||
*/
|
*/
|
||||||
public function __construct( $meta_key ) {
|
public function __construct( $meta_key ) {
|
||||||
$this->external_stock_key = $meta_key;
|
$this->external_stock_key = $meta_key;
|
||||||
|
$this->order_item_stock_source_key = '_siti_stock_source_breakdown';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,11 +33,15 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
public function register_hooks() {
|
public function register_hooks() {
|
||||||
add_action( 'woocommerce_product_options_stock_fields', array( $this, 'render_external_stock_field' ) );
|
add_action( 'woocommerce_product_options_stock_fields', array( $this, 'render_external_stock_field' ) );
|
||||||
add_action( 'woocommerce_admin_process_product_object', array( $this, 'save_external_stock_value' ) );
|
add_action( 'woocommerce_admin_process_product_object', array( $this, 'save_external_stock_value' ) );
|
||||||
|
add_action( 'woocommerce_variation_options_inventory', array( $this, 'render_variation_external_stock_field' ), 10, 3 );
|
||||||
|
add_action( 'woocommerce_save_product_variation', array( $this, 'save_variation_external_stock_value' ), 10, 2 );
|
||||||
add_filter( 'woocommerce_product_get_stock_quantity', array( $this, 'filter_stock_quantity_with_external' ), 10, 2 );
|
add_filter( 'woocommerce_product_get_stock_quantity', array( $this, 'filter_stock_quantity_with_external' ), 10, 2 );
|
||||||
add_filter( 'woocommerce_product_variation_get_stock_quantity', array( $this, 'filter_stock_quantity_with_external' ), 10, 2 );
|
add_filter( 'woocommerce_product_variation_get_stock_quantity', array( $this, 'filter_stock_quantity_with_external' ), 10, 2 );
|
||||||
add_filter( 'woocommerce_product_get_stock_status', array( $this, 'filter_stock_status_with_external' ), 10, 2 );
|
add_filter( 'woocommerce_product_get_stock_status', array( $this, 'filter_stock_status_with_external' ), 10, 2 );
|
||||||
add_filter( 'woocommerce_product_variation_get_stock_status', array( $this, 'filter_stock_status_with_external' ), 10, 2 );
|
add_filter( 'woocommerce_product_variation_get_stock_status', array( $this, 'filter_stock_status_with_external' ), 10, 2 );
|
||||||
add_action( 'woocommerce_reduce_order_item_stock', array( $this, 'rebalance_stock_after_order_reduction' ), 20, 3 );
|
add_action( 'woocommerce_reduce_order_item_stock', array( $this, 'rebalance_stock_after_order_reduction' ), 20, 3 );
|
||||||
|
add_action( 'woocommerce_after_order_itemmeta', array( $this, 'render_order_item_stock_source' ), 10, 3 );
|
||||||
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_order_admin_assets' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,6 +97,67 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
$product->update_meta_data( $this->external_stock_key, $value );
|
$product->update_meta_data( $this->external_stock_key, $value );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the external stock field for each variation row.
|
||||||
|
*
|
||||||
|
* @param int $loop Loop index.
|
||||||
|
* @param array $variation_data Raw variation data.
|
||||||
|
* @param WP_Post $variation Variation post object.
|
||||||
|
*/
|
||||||
|
public function render_variation_external_stock_field( $loop, $variation_data, $variation ) {
|
||||||
|
unset( $variation_data );
|
||||||
|
|
||||||
|
if ( ! function_exists( 'woocommerce_wp_text_input' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product = $variation instanceof WC_Product ? $variation : wc_get_product( $variation->ID );
|
||||||
|
|
||||||
|
if ( ! $product instanceof WC_Product ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$external_stock = $this->get_external_stock( $product );
|
||||||
|
$field_key = 'variable_' . $this->external_stock_key;
|
||||||
|
|
||||||
|
woocommerce_wp_text_input(
|
||||||
|
array(
|
||||||
|
'id' => $this->external_stock_key . '_' . $loop,
|
||||||
|
'name' => $field_key . '[' . $loop . ']',
|
||||||
|
'value' => $external_stock,
|
||||||
|
'label' => __( 'Externe voorraad', 'siti-stock-plugin' ),
|
||||||
|
'desc_tip' => true,
|
||||||
|
'description' => __( 'Voorraad beschikbaar op externe locatie(s).', 'siti-stock-plugin' ),
|
||||||
|
'type' => 'number',
|
||||||
|
'wrapper_class' => 'form-row form-row-full',
|
||||||
|
'custom_attributes' => array(
|
||||||
|
'step' => '1',
|
||||||
|
'min' => '0',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist external stock for variations.
|
||||||
|
*
|
||||||
|
* @param int $variation_id Variation post ID.
|
||||||
|
* @param int $index Loop index.
|
||||||
|
*/
|
||||||
|
public function save_variation_external_stock_value( $variation_id, $index ) {
|
||||||
|
$field_key = 'variable_' . $this->external_stock_key;
|
||||||
|
|
||||||
|
if ( ! isset( $_POST[ $field_key ][ $index ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw_value = wp_unslash( $_POST[ $field_key ][ $index ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||||
|
$value = function_exists( 'wc_stock_amount' ) ? wc_stock_amount( $raw_value ) : (int) $raw_value;
|
||||||
|
$value = max( 0, (int) $value );
|
||||||
|
|
||||||
|
update_post_meta( $variation_id, $this->external_stock_key, $value );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure WooCommerce exposes combined stock (local + external) on the frontend.
|
* Ensure WooCommerce exposes combined stock (local + external) on the frontend.
|
||||||
*
|
*
|
||||||
@@ -153,6 +224,71 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
return max( 0, (int) $base_stock ) + $external_stock;
|
return max( 0, (int) $base_stock ) + $external_stock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the quantity reduced for the order item.
|
||||||
|
*
|
||||||
|
* @param WC_Order_Item_Product $item Order line item.
|
||||||
|
* @param array<string,mixed> $change Change context from WooCommerce.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private function determine_reduced_quantity( $item, $change ) {
|
||||||
|
if ( ! is_array( $change ) ) {
|
||||||
|
$change = array(
|
||||||
|
'quantity' => $change,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$from = isset( $change['from'] ) ? $change['from'] : null;
|
||||||
|
$to = isset( $change['to'] ) ? $change['to'] : null;
|
||||||
|
|
||||||
|
if ( null !== $from && null !== $to ) {
|
||||||
|
$qty = $from - $to;
|
||||||
|
} elseif ( isset( $change['quantity'] ) ) {
|
||||||
|
$qty = $change['quantity'];
|
||||||
|
} elseif ( $item instanceof WC_Order_Item_Product ) {
|
||||||
|
$qty = $item->get_quantity();
|
||||||
|
} else {
|
||||||
|
$qty = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$qty = function_exists( 'wc_stock_amount' ) ? wc_stock_amount( $qty ) : (int) $qty;
|
||||||
|
|
||||||
|
return max( 0, (int) $qty );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record the stock source split for an order item.
|
||||||
|
*
|
||||||
|
* @param WC_Order_Item_Product $item Order line item.
|
||||||
|
* @param WC_Product $stock_holder Product that holds stock.
|
||||||
|
* @param int $quantity Total reduced quantity.
|
||||||
|
*/
|
||||||
|
private function record_order_item_stock_source_breakdown( $item, $stock_holder, $quantity ) {
|
||||||
|
$previous_local = (int) $stock_holder->get_stock_quantity( 'edit' ) + $quantity;
|
||||||
|
$local_available = max( 0, $previous_local );
|
||||||
|
$external_available = (int) $this->get_external_stock( $stock_holder );
|
||||||
|
$from_local = min( $quantity, $local_available );
|
||||||
|
$remaining = max( 0, $quantity - $from_local );
|
||||||
|
$from_external = min( $remaining, max( 0, $external_available ) );
|
||||||
|
|
||||||
|
$existing = $item->get_meta( $this->order_item_stock_source_key, true );
|
||||||
|
$existing = is_array( $existing )
|
||||||
|
? array(
|
||||||
|
'local' => isset( $existing['local'] ) ? (int) $existing['local'] : 0,
|
||||||
|
'external' => isset( $existing['external'] ) ? (int) $existing['external'] : 0,
|
||||||
|
)
|
||||||
|
: array(
|
||||||
|
'local' => 0,
|
||||||
|
'external' => 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
$existing['local'] += $from_local;
|
||||||
|
$existing['external'] += $from_external;
|
||||||
|
|
||||||
|
$item->update_meta_data( $this->order_item_stock_source_key, $existing );
|
||||||
|
$item->save();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure local stock only dips below zero when the external stock is depleted.
|
* Ensure local stock only dips below zero when the external stock is depleted.
|
||||||
*
|
*
|
||||||
@@ -161,7 +297,7 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
* @param WC_Order $order Order instance (unused).
|
* @param WC_Order $order Order instance (unused).
|
||||||
*/
|
*/
|
||||||
public function rebalance_stock_after_order_reduction( $item, $change, $order ) {
|
public function rebalance_stock_after_order_reduction( $item, $change, $order ) {
|
||||||
unset( $change, $order );
|
unset( $order );
|
||||||
|
|
||||||
if ( ! $item instanceof WC_Order_Item_Product ) {
|
if ( ! $item instanceof WC_Order_Item_Product ) {
|
||||||
return;
|
return;
|
||||||
@@ -180,6 +316,12 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$quantity = $this->determine_reduced_quantity( $item, $change );
|
||||||
|
|
||||||
|
if ( $quantity > 0 ) {
|
||||||
|
$this->record_order_item_stock_source_breakdown( $item, $stock_holder, $quantity );
|
||||||
|
}
|
||||||
|
|
||||||
$current_local = (int) $stock_holder->get_stock_quantity( 'edit' );
|
$current_local = (int) $stock_holder->get_stock_quantity( 'edit' );
|
||||||
$external_stock = $this->get_external_stock( $stock_holder );
|
$external_stock = $this->get_external_stock( $stock_holder );
|
||||||
|
|
||||||
@@ -206,4 +348,91 @@ class Siti_Stock_Inventory_Manager {
|
|||||||
$stock_holder->save();
|
$stock_holder->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the recorded stock source on the admin order screen.
|
||||||
|
*
|
||||||
|
* @param int $item_id Order item ID.
|
||||||
|
* @param WC_Order_Item $item Order item instance.
|
||||||
|
* @param WC_Product|false $product Product (unused).
|
||||||
|
*/
|
||||||
|
public function render_order_item_stock_source( $item_id, $item, $product ) {
|
||||||
|
unset( $item_id, $product );
|
||||||
|
|
||||||
|
if ( ! is_admin() || ! $item instanceof WC_Order_Item_Product ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $item->get_meta( $this->order_item_stock_source_key, true );
|
||||||
|
|
||||||
|
if ( ! is_array( $data ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$local = isset( $data['local'] ) ? max( 0, (int) $data['local'] ) : 0;
|
||||||
|
$external = isset( $data['external'] ) ? max( 0, (int) $data['external'] ) : 0;
|
||||||
|
|
||||||
|
if ( $local <= 0 && $external <= 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<div class="siti-stock-order-item-sources">';
|
||||||
|
echo '<span class="siti-stock-order-item-sources__label">' . esc_html__( 'Voorraadbron', 'siti-stock-plugin' ) . ':</span>';
|
||||||
|
|
||||||
|
if ( $local > 0 ) {
|
||||||
|
$local_label = sprintf(
|
||||||
|
/* translators: %d: quantity fulfilled from regular stock. */
|
||||||
|
_n( '%d uit reguliere voorraad', '%d uit reguliere voorraad', $local, 'siti-stock-plugin' ),
|
||||||
|
$local
|
||||||
|
);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<span class="siti-stock-order-item-sources__badge is-local"><span class="dashicons dashicons-archive" aria-hidden="true"></span>%s</span>',
|
||||||
|
esc_html( $local_label )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $external > 0 ) {
|
||||||
|
$external_label = sprintf(
|
||||||
|
/* translators: %d: quantity fulfilled from external stock. */
|
||||||
|
_n( '%d uit externe voorraad', '%d uit externe voorraad', $external, 'siti-stock-plugin' ),
|
||||||
|
$external
|
||||||
|
);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<span class="siti-stock-order-item-sources__badge is-external"><span class="dashicons dashicons-cloud" aria-hidden="true"></span>%s</span>',
|
||||||
|
esc_html( $external_label )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enqueue lightweight styling on the order edit screen.
|
||||||
|
*
|
||||||
|
* @param string $hook Current admin hook.
|
||||||
|
*/
|
||||||
|
public function enqueue_order_admin_assets( $hook ) {
|
||||||
|
if ( 'post.php' !== $hook && 'post-new.php' !== $hook ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! function_exists( 'get_current_screen' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$screen = get_current_screen();
|
||||||
|
|
||||||
|
if ( ! $screen || 'shop_order' !== $screen->post_type ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_style(
|
||||||
|
'siti-stock-order-admin',
|
||||||
|
plugins_url( 'assets/css/order-admin.css', SITI_STOCK_PLUGIN_FILE ),
|
||||||
|
array(),
|
||||||
|
SITI_STOCK_PLUGIN_VERSION
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: Siti Stock Plugin
|
* Plugin Name: Siti Stock Plugin
|
||||||
* Plugin URI: https://github.com/SitiWeb/siti-stock-plugin
|
* Plugin URI: https://github.com/SitiWeb/siti-stock-plugin
|
||||||
* Description: Synchroniseert WooCommerce voorraad met het externe Siti voorraadplatform.
|
* Description: Synchroniseert WooCommerce voorraad met het externe Siti voorraadplatform.
|
||||||
* Version: 1.0.0
|
* Version: 1.2.1
|
||||||
* Author: Siti Web
|
* Author: Siti Web
|
||||||
* Author URI: https://www.siti.nl
|
* Author URI: https://www.siti.nl
|
||||||
* Requires PHP: 8.1
|
* Requires PHP: 8.1
|
||||||
@@ -16,12 +16,15 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'SITI_STOCK_PLUGIN_VERSION', '0.1.0' );
|
define( 'SITI_STOCK_PLUGIN_VERSION', '1.2.0' );
|
||||||
define( 'SITI_STOCK_PLUGIN_FILE', __FILE__ );
|
define( 'SITI_STOCK_PLUGIN_FILE', __FILE__ );
|
||||||
define( 'SITI_STOCK_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'SITI_STOCK_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
|
|
||||||
require_once __DIR__ . '/includes/class-siti-stock-plugin.php';
|
require_once __DIR__ . '/includes/class-siti-stock-plugin.php';
|
||||||
|
|
||||||
|
if ( ! class_exists( 'SitiWebUpdater' ) ) {
|
||||||
require_once __DIR__ . '/SitiWebUpdater.php';
|
require_once __DIR__ . '/SitiWebUpdater.php';
|
||||||
|
}
|
||||||
|
|
||||||
register_activation_hook( __FILE__, array( 'Siti_Stock_Plugin', 'activate' ) );
|
register_activation_hook( __FILE__, array( 'Siti_Stock_Plugin', 'activate' ) );
|
||||||
register_deactivation_hook( __FILE__, array( 'Siti_Stock_Plugin', 'deactivate' ) );
|
register_deactivation_hook( __FILE__, array( 'Siti_Stock_Plugin', 'deactivate' ) );
|
||||||
|
|||||||
Reference in New Issue
Block a user