Custom WooCommerce theme for orgsteklo.ru including: - Product catalog with category/subcategory hierarchy - Custom checkout with delivery calculation - Price calculator - Admin settings panel - Search functionality - User account pages
1020 lines
41 KiB
PHP
1020 lines
41 KiB
PHP
<?php
|
||
/**
|
||
* Функции для расчета цен на оргстекло
|
||
* Реализует Алгоритм 1 (стандартные размеры) и Алгоритм 2 (заданные размеры)
|
||
*/
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
// ========== ЗАЩИТА ОТ БИТЫХ ЭЛЕМЕНТОВ КОРЗИНЫ ==========
|
||
// Удаляем битые элементы при загрузке корзины из сессии
|
||
add_action( 'woocommerce_cart_loaded_from_session', 'orgsteklo_remove_broken_items', 999 );
|
||
function orgsteklo_remove_broken_items( $cart ) {
|
||
$to_remove = array();
|
||
|
||
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
|
||
// Проверяем что элемент корректный
|
||
if ( ! isset( $cart_item['product_id'] ) || ! isset( $cart_item['data'] ) || ! is_object( $cart_item['data'] ) ) {
|
||
$to_remove[] = $cart_item_key;
|
||
}
|
||
}
|
||
|
||
// Удаляем битые элементы
|
||
if ( ! empty( $to_remove ) ) {
|
||
foreach ( $to_remove as $key ) {
|
||
unset( $cart->cart_contents[ $key ] );
|
||
}
|
||
$cart->set_session();
|
||
}
|
||
}
|
||
|
||
// ВРЕМЕННАЯ ОЧИСТКА И КОНВЕРТАЦИЯ - ЗАКОММЕНТИРОВАНО!
|
||
/*
|
||
add_action( 'init', 'orgsteklo_clear_and_convert', 1 );
|
||
function orgsteklo_clear_and_convert() {
|
||
global $wpdb;
|
||
|
||
// Очищаем корзину
|
||
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_wc_session_%'" );
|
||
$wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_wc_session_%'" );
|
||
$wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE '_woocommerce_persistent_cart%'" );
|
||
if ( function_exists( 'WC' ) && WC()->cart ) {
|
||
WC()->cart->empty_cart();
|
||
}
|
||
|
||
// Находим все товары с калькулятором и меняем их тип на simple
|
||
$products_with_calculator = $wpdb->get_results(
|
||
"SELECT post_id FROM {$wpdb->postmeta}
|
||
WHERE meta_key = '_orgsteklo_price_table'
|
||
AND meta_value != ''"
|
||
);
|
||
|
||
foreach ( $products_with_calculator as $row ) {
|
||
$product_id = $row->post_id;
|
||
|
||
// Меняем тип на simple
|
||
wp_set_object_terms( $product_id, 'simple', 'product_type' );
|
||
update_post_meta( $product_id, '_product_type', 'simple' );
|
||
|
||
// Удаляем все вариации если есть
|
||
$variations = $wpdb->get_results( $wpdb->prepare(
|
||
"SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'product_variation'",
|
||
$product_id
|
||
) );
|
||
|
||
foreach ( $variations as $variation ) {
|
||
wp_delete_post( $variation->ID, true );
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
// ========== КОНЕЦ - ВРЕМЕННАЯ ОЧИСТКА ЗАКОММЕНТИРОВАНА ==========
|
||
|
||
/**
|
||
* Получение констант из настроек (с возможностью изменения в админке)
|
||
*/
|
||
|
||
function orgsteklo_get_discount_percent_from_tags( int $product_id ): int {
|
||
$tags = wp_get_post_terms( $product_id, 'product_tag', ['fields' => 'names'] );
|
||
|
||
if ( empty( $tags ) || is_wp_error( $tags ) ) {
|
||
return 0;
|
||
}
|
||
|
||
foreach ( $tags as $tag ) {
|
||
if ( preg_match('/-(\d{1,2})%/', $tag, $m) ) {
|
||
return min( (int) $m[1], 90 ); // защита от -100%
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
function orgsteklo_get_material_density() {
|
||
return floatval( get_option( 'orgsteklo_material_density', 1.19 ) );
|
||
}
|
||
|
||
function orgsteklo_get_custom_size_coefficient() {
|
||
return floatval( get_option( 'orgsteklo_custom_size_coefficient', 1.2 ) );
|
||
}
|
||
|
||
/**
|
||
* Получить данные таблицы для товара
|
||
*
|
||
* @param int $product_id ID товара
|
||
* @return array|null Данные таблицы или null если не найдено
|
||
*/
|
||
function orgsteklo_get_product_table_data( $product_id ) {
|
||
$table_number = get_post_meta( $product_id, '_orgsteklo_price_table', true );
|
||
|
||
if ( empty( $table_number ) ) {
|
||
return null;
|
||
}
|
||
|
||
$tables = get_option( 'orgsteklo_price_tables', [] );
|
||
|
||
if ( ! isset( $tables[ $table_number ] ) ) {
|
||
return null;
|
||
}
|
||
|
||
return $tables[ $table_number ];
|
||
}
|
||
|
||
/**
|
||
* Получить строку данных из таблицы по толщине
|
||
*
|
||
* @param array $table_data Данные таблицы
|
||
* @param float $thickness Толщина листа в мм
|
||
* @return array|null Строка данных или null если не найдено
|
||
*/
|
||
function orgsteklo_get_table_row_by_thickness( $table_data, $thickness ) {
|
||
if ( empty( $table_data['rows'] ) ) {
|
||
return null;
|
||
}
|
||
|
||
foreach ( $table_data['rows'] as $row ) {
|
||
if ( abs( $row['thickness'] - $thickness ) < 0.01 ) {
|
||
return $row;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Расчет веса 1 листа
|
||
* Формула: В = А × В × С × 1.19 / 1000000
|
||
*
|
||
* @param float $width Ширина в мм
|
||
* @param float $length Длина в мм
|
||
* @param float $thickness Толщина в мм
|
||
* @return float Вес в кг (округлено до 3 знаков)
|
||
*/
|
||
function orgsteklo_calculate_sheet_weight( $width, $length, $thickness ) {
|
||
$density = orgsteklo_get_material_density();
|
||
$weight = ( $width * $length * $thickness * $density ) / 1000000;
|
||
return round( $weight, 3 );
|
||
}
|
||
|
||
/**
|
||
* Расчет веса 1 м²
|
||
* Формула: В_М² = С × 1.19
|
||
*
|
||
* @param float $thickness Толщина в мм
|
||
* @return float Вес в кг (округлено до 3 знаков)
|
||
*/
|
||
function orgsteklo_calculate_sqm_weight( $thickness ) {
|
||
$density = orgsteklo_get_material_density();
|
||
$weight = $thickness * $density;
|
||
return round( $weight, 3 );
|
||
}
|
||
|
||
/**
|
||
* Расчет стоимости 1 кг для листа стандартного размера
|
||
* Формула: Ст.кг_ЛСР = Ст.кг_баз × К1 × К2 × К3 × К4 × К5
|
||
*
|
||
* @param array $row Строка данных из таблицы
|
||
* @return float Стоимость в рублях (округлено до целых)
|
||
*/
|
||
function orgsteklo_calculate_standard_price_per_kg( $row ) {
|
||
$base_price = get_option( 'orgsteklo_base_price_per_kg', 300 );
|
||
|
||
$k1 = $row['k1'] ?? 1;
|
||
$k2 = $row['k2'] ?? 1;
|
||
$k3 = $row['k3'] ?? 1;
|
||
$k4 = $row['k4'] ?? 1;
|
||
$k5 = $row['k5'] ?? 1;
|
||
|
||
$price = $base_price * $k1 * $k2 * $k3 * $k4 * $k5;
|
||
return round( $price );
|
||
}
|
||
|
||
/**
|
||
* АЛГОРИТМ 1: Расчет для листа стандартного размера
|
||
*
|
||
* @param int $product_id ID товара
|
||
* @param float $thickness Толщина в мм
|
||
* @param int $quantity Количество листов
|
||
* @return array|WP_Error Массив с результатами расчета или ошибка
|
||
*/
|
||
function orgsteklo_calculate_standard_size( $product_id, $thickness, $quantity = 1 ) {
|
||
// Получаем данные таблицы
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
if ( ! $table_data ) {
|
||
return new WP_Error( 'no_table', 'Для товара не выбрана таблица расчета цен' );
|
||
}
|
||
|
||
// Получаем строку данных по толщине
|
||
$row = orgsteklo_get_table_row_by_thickness( $table_data, $thickness );
|
||
if ( ! $row ) {
|
||
return new WP_Error( 'no_thickness', 'В таблице нет данных для толщины ' . $thickness . ' мм' );
|
||
}
|
||
|
||
// Базовая стоимость 1 кг
|
||
$base_price_kg = get_option( 'orgsteklo_base_price_per_kg', 300 );
|
||
|
||
// Шаг 2: Стоимость 1 кг для листа стандартного размера
|
||
$price_per_kg_standard = orgsteklo_calculate_standard_price_per_kg( $row );
|
||
|
||
// Шаг 3: Вес 1 листа стандартного размера
|
||
$weight_standard_sheet = orgsteklo_calculate_sheet_weight(
|
||
$row['width'],
|
||
$row['length'],
|
||
$row['thickness']
|
||
);
|
||
|
||
// Шаг 4: Стоимость 1 листа стандартного размера
|
||
$price_standard_sheet = round( $price_per_kg_standard * $weight_standard_sheet );
|
||
|
||
// Шаг 6: Вес заказа
|
||
$order_weight = round( $weight_standard_sheet * $quantity, 3 );
|
||
|
||
// Шаг 7: Стоимость заказа
|
||
$order_price = round( $price_standard_sheet * $quantity );
|
||
|
||
// Шаг 8: Вес 1 м²
|
||
$weight_sqm = orgsteklo_calculate_sqm_weight( $row['thickness'] );
|
||
|
||
// Шаг 9: Стоимость 1 м²
|
||
$price_sqm = round( ( $price_standard_sheet * 1000000 ) / ( $row['width'] * $row['length'] ) );
|
||
|
||
return [
|
||
'is_standard_size' => true,
|
||
'base_price_kg' => $base_price_kg,
|
||
'price_per_kg_standard' => $price_per_kg_standard,
|
||
'weight_standard_sheet' => $weight_standard_sheet,
|
||
'price_standard_sheet' => $price_standard_sheet,
|
||
'width' => $row['width'],
|
||
'length' => $row['length'],
|
||
'thickness' => $row['thickness'],
|
||
'price_per_kg_current' => $price_per_kg_standard, // Для стандартного = standard
|
||
'weight_current_sheet' => $weight_standard_sheet, // Для стандартного = standard
|
||
'price_current_sheet' => $price_standard_sheet, // Для стандартного = standard
|
||
'weight_sqm' => $weight_sqm,
|
||
'price_sqm' => $price_sqm,
|
||
'quantity' => $quantity,
|
||
'order_weight' => $order_weight,
|
||
'order_price' => $order_price,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* АЛГОРИТМ 2: Расчет для листа заданного размера
|
||
*
|
||
* @param int $product_id ID товара
|
||
* @param float $thickness Толщина в мм
|
||
* @param float $width Заданная ширина в мм
|
||
* @param float $length Заданная длина в мм
|
||
* @param int $quantity Количество листов
|
||
* @return array|WP_Error Массив с результатами расчета или ошибка
|
||
*/
|
||
function orgsteklo_calculate_custom_size( $product_id, $thickness, $width, $length, $quantity = 1 ) {
|
||
// Сначала получаем все значения из Алгоритма 1 (они не меняются)
|
||
$standard_calc = orgsteklo_calculate_standard_size( $product_id, $thickness, $quantity );
|
||
|
||
if ( is_wp_error( $standard_calc ) ) {
|
||
return $standard_calc;
|
||
}
|
||
|
||
// Получаем данные таблицы для надбавки N
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
$row = orgsteklo_get_table_row_by_thickness( $table_data, $thickness );
|
||
|
||
// Шаг 1: Вес 1 листа заданного размера
|
||
$weight_custom_sheet = orgsteklo_calculate_sheet_weight( $width, $length, $thickness );
|
||
|
||
// Шаг 2: Вес заказа
|
||
$order_weight = round( $weight_custom_sheet * $quantity, 3 );
|
||
|
||
// Шаг 3: Стоимость 1 листа заданного размера
|
||
// Формула: Ст.ЛЗР = (Ст.кг_ЛСР × коэфф × В_ЛЗР) + N
|
||
$n_surcharge = $row['n'] ?? 0;
|
||
$custom_coeff = orgsteklo_get_custom_size_coefficient();
|
||
$price_custom_sheet = round(
|
||
( $standard_calc['price_per_kg_standard'] * $custom_coeff * $weight_custom_sheet ) + $n_surcharge
|
||
);
|
||
|
||
// Шаг 4: Стоимость заказа и стоимость 1 м²
|
||
$order_price = round( $price_custom_sheet * $quantity );
|
||
$price_sqm = round( ( $price_custom_sheet * 1000000 ) / ( $width * $length ) );
|
||
|
||
// Шаг 5: Стоимость 1 кг для листа заданного размера
|
||
$price_per_kg_custom = $order_weight > 0 ? round( $order_price / $order_weight ) : 0;
|
||
|
||
// Обновляем результаты
|
||
$result = $standard_calc;
|
||
$result['is_standard_size'] = false;
|
||
$result['width'] = $width;
|
||
$result['length'] = $length;
|
||
$result['weight_current_sheet'] = $weight_custom_sheet;
|
||
$result['price_current_sheet'] = $price_custom_sheet;
|
||
$result['price_per_kg_current'] = $price_per_kg_custom;
|
||
$result['price_sqm'] = $price_sqm;
|
||
$result['order_weight'] = $order_weight;
|
||
$result['order_price'] = $order_price;
|
||
$result['n_surcharge'] = $n_surcharge;
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Универсальная функция расчета - автоматически определяет алгоритм
|
||
*
|
||
* @param int $product_id ID товара
|
||
* @param float $thickness Толщина в мм
|
||
* @param float|null $width Ширина в мм (null = стандартная)
|
||
* @param float|null $length Длина в мм (null = стандартная)
|
||
* @param int $quantity Количество листов
|
||
* @return array|WP_Error Массив с результатами расчета или ошибка
|
||
*/
|
||
function orgsteklo_calculate_price( $product_id, $thickness, $width = null, $length = null, $quantity = 1 ) {
|
||
// Получаем данные таблицы
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
if ( ! $table_data ) {
|
||
return new WP_Error( 'no_table', 'Для товара не выбрана таблица расчета цен' );
|
||
}
|
||
|
||
// Получаем строку данных по толщине
|
||
$row = orgsteklo_get_table_row_by_thickness( $table_data, $thickness );
|
||
if ( ! $row ) {
|
||
return new WP_Error( 'no_thickness', 'В таблице нет данных для толщины ' . $thickness . ' мм' );
|
||
}
|
||
|
||
// Определяем стандартные размеры
|
||
$standard_width = $row['width'];
|
||
$standard_length = $row['length'];
|
||
|
||
// Если размеры не заданы или равны стандартным - используем Алгоритм 1
|
||
if (
|
||
( $width === null && $length === null ) ||
|
||
( abs( $width - $standard_width ) < 0.01 && abs( $length - $standard_length ) < 0.01 )
|
||
) {
|
||
return orgsteklo_calculate_standard_size( $product_id, $thickness, $quantity );
|
||
}
|
||
|
||
// Иначе используем Алгоритм 2
|
||
return orgsteklo_calculate_custom_size( $product_id, $thickness, $width, $length, $quantity );
|
||
}
|
||
|
||
/**
|
||
* Получить все доступные толщины для товара
|
||
*
|
||
* @param int $product_id ID товара
|
||
* @return array Массив толщин
|
||
*/
|
||
function orgsteklo_get_available_thicknesses( $product_id ) {
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
|
||
if ( ! $table_data || empty( $table_data['rows'] ) ) {
|
||
return [];
|
||
}
|
||
|
||
$thicknesses = [];
|
||
foreach ( $table_data['rows'] as $row ) {
|
||
$thicknesses[] = $row['thickness'];
|
||
}
|
||
|
||
return $thicknesses;
|
||
}
|
||
|
||
/**
|
||
* Получить стандартные размеры для толщины
|
||
*
|
||
* @param int $product_id ID товара
|
||
* @param float $thickness Толщина в мм
|
||
* @return array|null Массив ['width' => ..., 'length' => ...] или null
|
||
*/
|
||
function orgsteklo_get_standard_dimensions( $product_id, $thickness ) {
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
|
||
if ( ! $table_data ) {
|
||
return null;
|
||
}
|
||
|
||
$row = orgsteklo_get_table_row_by_thickness( $table_data, $thickness );
|
||
|
||
if ( ! $row ) {
|
||
return null;
|
||
}
|
||
|
||
return [
|
||
'width' => $row['width'],
|
||
'length' => $row['length'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* AJAX обработчик для расчета цен
|
||
*/
|
||
add_action( 'wp_ajax_orgsteklo_calculate', 'orgsteklo_ajax_calculate' );
|
||
add_action( 'wp_ajax_nopriv_orgsteklo_calculate', 'orgsteklo_ajax_calculate' );
|
||
|
||
function orgsteklo_ajax_calculate() {
|
||
|
||
check_ajax_referer( 'orgsteklo_calc_nonce', 'nonce' );
|
||
|
||
$product_id = intval( $_POST['product_id'] ?? 0 );
|
||
$thickness = floatval( $_POST['thickness'] ?? 0 );
|
||
$width = isset($_POST['width']) ? floatval($_POST['width']) : null;
|
||
$length = isset($_POST['length']) ? floatval($_POST['length']) : null;
|
||
$quantity = intval( $_POST['quantity'] ?? 1 );
|
||
|
||
if ( ! $product_id || ! $thickness ) {
|
||
wp_send_json_error( [ 'message' => 'Не указаны обязательные параметры', 'debug' => 'params missing' ] );
|
||
}
|
||
|
||
/** 1️⃣ Получаем данные из таблицы калькулятора (как раньше!) */
|
||
$result = orgsteklo_calculate_price( $product_id, $thickness, $width, $length, $quantity );
|
||
|
||
if ( is_wp_error( $result ) ) {
|
||
wp_send_json_error( [ 'message' => $result->get_error_message() ] );
|
||
}
|
||
|
||
/** 2️⃣ Получаем стандартные размеры из таблицы для вывода */
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
$row = orgsteklo_get_table_row_by_thickness( $table_data, $thickness );
|
||
|
||
// Добавляем стандартные размеры в результат
|
||
$result['standard_width'] = $row['width'];
|
||
$result['standard_length'] = $row['length'];
|
||
|
||
/** 3️⃣ СКИДКА ПО МЕТКАМ */
|
||
$discount_percent = orgsteklo_get_discount_percent_from_tags( $product_id );
|
||
$has_discount = $discount_percent > 0;
|
||
|
||
$result['has_discount'] = $has_discount;
|
||
|
||
// гарантируем ключи
|
||
$result['price_standard_sheet_old'] = null;
|
||
$result['price_per_kg_standard_old'] = null;
|
||
$result['price_per_kg_current_old'] = null;
|
||
$result['price_sqm_old'] = null;
|
||
$result['price_current_sheet_old'] = null;
|
||
$result['order_price_old'] = null;
|
||
|
||
if ( $has_discount ) {
|
||
|
||
// сохраняем старые цены
|
||
$result['price_standard_sheet_old'] = $result['price_standard_sheet'];
|
||
$result['price_per_kg_standard_old'] = $result['price_per_kg_standard'];
|
||
$result['price_sqm_old'] = $result['price_sqm'];
|
||
$result['price_current_sheet_old'] = $result['price_current_sheet'];
|
||
$result['order_price_old'] = $result['order_price'];
|
||
|
||
// Сохраняем старую цену за кг для заданного размера (если есть)
|
||
if ( isset( $result['price_per_kg_current'] ) ) {
|
||
$result['price_per_kg_current_old'] = $result['price_per_kg_current'];
|
||
}
|
||
|
||
$coef = (100 - $discount_percent) / 100;
|
||
|
||
// применяем скидку
|
||
$result['price_standard_sheet'] = round($result['price_standard_sheet'] * $coef);
|
||
$result['price_per_kg_standard'] = round($result['price_per_kg_standard'] * $coef);
|
||
$result['price_sqm'] = round($result['price_sqm'] * $coef);
|
||
$result['price_current_sheet'] = round($result['price_current_sheet'] * $coef);
|
||
$result['order_price'] = round($result['order_price'] * $coef);
|
||
|
||
// Применяем скидку к цене за кг для заданного размера (если есть)
|
||
if ( isset( $result['price_per_kg_current'] ) ) {
|
||
$result['price_per_kg_current'] = round($result['price_per_kg_current'] * $coef);
|
||
}
|
||
}
|
||
|
||
/** 4️⃣ Отправляем результат */
|
||
wp_send_json_success( $result );
|
||
}
|
||
/**
|
||
* AJAX обработчик для получения стандартных размеров
|
||
*/
|
||
add_action( 'wp_ajax_orgsteklo_get_standard_dimensions', 'orgsteklo_ajax_get_standard_dimensions' );
|
||
add_action( 'wp_ajax_nopriv_orgsteklo_get_standard_dimensions', 'orgsteklo_ajax_get_standard_dimensions' );
|
||
|
||
function orgsteklo_ajax_get_standard_dimensions() {
|
||
check_ajax_referer( 'orgsteklo_calc_nonce', 'nonce' );
|
||
|
||
$product_id = intval( $_POST['product_id'] ?? 0 );
|
||
$thickness = floatval( $_POST['thickness'] ?? 0 );
|
||
|
||
if ( ! $product_id || ! $thickness ) {
|
||
wp_send_json_error( [ 'message' => 'Не указаны обязательные параметры' ] );
|
||
}
|
||
|
||
/** Получаем стандартные размеры из таблицы (как раньше!) */
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
if ( ! $table_data ) {
|
||
wp_send_json_error( [ 'message' => 'Для товара не выбрана таблица расчета цен' ] );
|
||
}
|
||
|
||
$row = orgsteklo_get_table_row_by_thickness( $table_data, $thickness );
|
||
if ( ! $row ) {
|
||
wp_send_json_error( [ 'message' => 'В таблице нет данных для толщины ' . $thickness . ' мм' ] );
|
||
}
|
||
|
||
wp_send_json_success( [
|
||
'width' => floatval( $row['width'] ),
|
||
'length' => floatval( $row['length'] ),
|
||
] );
|
||
}
|
||
|
||
/**
|
||
* Подключение скриптов калькулятора на странице товара
|
||
*/
|
||
add_action( 'wp_enqueue_scripts', 'orgsteklo_enqueue_calculator_scripts' );
|
||
function orgsteklo_enqueue_calculator_scripts() {
|
||
// Только на странице товара
|
||
if ( ! is_product() ) {
|
||
return;
|
||
}
|
||
|
||
global $post;
|
||
$product_id = $post->ID;
|
||
|
||
// Проверяем что у товара выбрана таблица
|
||
$table_number = get_post_meta( $product_id, '_orgsteklo_price_table', true );
|
||
if ( empty( $table_number ) ) {
|
||
return;
|
||
}
|
||
|
||
// Подключаем скрипт
|
||
wp_enqueue_script(
|
||
'orgsteklo-price-calculator',
|
||
get_template_directory_uri() . '/assets/js/price-calculator.js',
|
||
[ 'jquery' ],
|
||
'1.0.1',
|
||
true
|
||
);
|
||
|
||
// Передаем данные в JS
|
||
wp_localize_script(
|
||
'orgsteklo-price-calculator',
|
||
'orgstekloCalc',
|
||
[
|
||
'productId' => $product_id,
|
||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||
'nonce' => wp_create_nonce( 'orgsteklo_calc_nonce' ),
|
||
]
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Подключение шаблона калькулятора для товаров с выбранной таблицей
|
||
*/
|
||
add_filter( 'woocommerce_locate_template', 'orgsteklo_use_calculator_template', 10, 3 );
|
||
function orgsteklo_use_calculator_template( $template, $template_name, $template_path ) {
|
||
// Проверяем что это шаблон вариативного или простого товара
|
||
if ( $template_name !== 'single-product/add-to-cart/variable.php' && $template_name !== 'single-product/add-to-cart/simple.php' ) {
|
||
return $template;
|
||
}
|
||
|
||
// Проверяем что мы на странице товара
|
||
if ( ! is_product() ) {
|
||
return $template;
|
||
}
|
||
|
||
global $post;
|
||
if ( ! $post ) {
|
||
return $template;
|
||
}
|
||
|
||
// Проверяем что у товара выбрана таблица калькулятора
|
||
$table_number = get_post_meta( $post->ID, '_orgsteklo_price_table', true );
|
||
if ( empty( $table_number ) ) {
|
||
return $template;
|
||
}
|
||
|
||
// Используем наш шаблон калькулятора
|
||
$calculator_template = get_template_directory() . '/woocommerce/single-product/add-to-cart/orgsteklo-calculator.php';
|
||
|
||
if ( file_exists( $calculator_template ) ) {
|
||
return $calculator_template;
|
||
}
|
||
|
||
return $template;
|
||
}
|
||
|
||
/**
|
||
* Отключаем проверку атрибутов для универсальных вариаций калькулятора
|
||
*/
|
||
add_filter( 'woocommerce_add_to_cart_validation', 'orgsteklo_skip_variation_validation', 10, 3 );
|
||
function orgsteklo_skip_variation_validation( $passed, $product_id, $quantity ) {
|
||
// Если это товар с калькулятором - пропускаем валидацию атрибутов
|
||
$table_number = get_post_meta( $product_id, '_orgsteklo_price_table', true );
|
||
|
||
if ( ! empty( $table_number ) ) {
|
||
// Очищаем ошибки связанные с атрибутами
|
||
wc_clear_notices();
|
||
return true;
|
||
}
|
||
|
||
return $passed;
|
||
}
|
||
|
||
/**
|
||
* Создаем вариации из таблицы калькулятора
|
||
*/
|
||
function orgsteklo_create_variations_from_table( $product_id ) {
|
||
// Проверяем что товар использует калькулятор
|
||
$table_number = get_post_meta( $product_id, '_orgsteklo_price_table', true );
|
||
if ( empty( $table_number ) ) {
|
||
return array( 'success' => false, 'message' => 'Таблица не выбрана' );
|
||
}
|
||
|
||
// Получаем данные таблицы
|
||
$table_data = orgsteklo_get_product_table_data( $product_id );
|
||
if ( ! $table_data || empty( $table_data['rows'] ) ) {
|
||
return array( 'success' => false, 'message' => 'Нет данных в таблице' );
|
||
}
|
||
|
||
// Убеждаемся что товар вариативный
|
||
wp_set_object_terms( $product_id, 'variable', 'product_type' );
|
||
|
||
// Получаем родительский товар
|
||
$product = wc_get_product( $product_id );
|
||
|
||
// Собираем все значения толщин как строки
|
||
$thickness_values = array();
|
||
foreach ( $table_data['rows'] as $row ) {
|
||
if ( ! empty( $row['thickness'] ) ) {
|
||
$thickness_values[] = floatval( $row['thickness'] ) . ' мм';
|
||
}
|
||
}
|
||
|
||
// Создаем атрибут на уровне товара (кастомный, не глобальный)
|
||
$attribute = new WC_Product_Attribute();
|
||
$attribute->set_id( 0 );
|
||
$attribute->set_name( 'Толщина листа' ); // Имя без pa_
|
||
$attribute->set_options( $thickness_values ); // Массив строк
|
||
$attribute->set_position( 0 );
|
||
$attribute->set_visible( true );
|
||
$attribute->set_variation( true );
|
||
$product->set_attributes( array( $attribute ) );
|
||
$product->save();
|
||
|
||
// Создаем вариации
|
||
$count = 0;
|
||
foreach ( $table_data['rows'] as $row ) {
|
||
if ( empty( $row['thickness'] ) ) continue;
|
||
|
||
$thickness = floatval( $row['thickness'] );
|
||
$thickness_string = $thickness . ' мм';
|
||
|
||
// Создаем вариацию
|
||
$variation = new WC_Product_Variation();
|
||
$variation->set_parent_id( $product_id );
|
||
$variation->set_status( 'publish' );
|
||
$variation->set_virtual( true );
|
||
$variation->set_manage_stock( false );
|
||
$variation->set_stock_status( 'instock' );
|
||
// Устанавливаем минимальную цену 1 ₽ и вес 0.001 кг чтобы WooCommerce не ругался
|
||
// Реальная цена и вес будут установлены через хуки
|
||
$variation->set_regular_price( 1 );
|
||
$variation->set_weight( 0.001 );
|
||
|
||
// Устанавливаем атрибут - slug атрибута и значение
|
||
$variation->set_attributes( array(
|
||
sanitize_title( 'Толщина листа' ) => $thickness_string
|
||
) );
|
||
|
||
$variation_id = $variation->save();
|
||
|
||
if ( $variation_id ) {
|
||
// Сохраняем ТОЛЬКО толщину для поиска вариации (вариация = заглушка для WooCommerce)
|
||
// Все расчеты делаются из таблицы, НЕ из вариации!
|
||
update_post_meta( $variation_id, '_orgsteklo_thickness', $thickness );
|
||
|
||
$count++;
|
||
}
|
||
}
|
||
|
||
return array( 'success' => true, 'message' => 'Создано вариаций: ' . $count, 'count' => $count );
|
||
}
|
||
|
||
|
||
/**
|
||
* Сохраняем данные калькулятора в корзине через cart_item_data
|
||
* Теперь данные передаются через 6-й параметр add_to_cart()
|
||
*/
|
||
add_filter( 'woocommerce_add_cart_item_data', 'orgsteklo_add_calculator_to_cart', 10, 3 );
|
||
function orgsteklo_add_calculator_to_cart( $cart_item_data, $product_id, $variation_id ) {
|
||
// Данные уже переданы через параметр cart_item_data в add_to_cart()
|
||
// Просто возвращаем их как есть
|
||
return $cart_item_data;
|
||
}
|
||
|
||
/**
|
||
* Устанавливаем цену и вес СРАЗУ при добавлении в корзину
|
||
*/
|
||
add_filter( 'woocommerce_add_cart_item', 'orgsteklo_set_price_on_add', 10, 2 );
|
||
function orgsteklo_set_price_on_add( $cart_item, $cart_item_key ) {
|
||
if ( isset( $cart_item['orgsteklo_calculator'] ) && isset( $cart_item['data'] ) ) {
|
||
$calc = $cart_item['orgsteklo_calculator'];
|
||
|
||
// Округляем до целых для гарантии отсутствия копеек
|
||
$price = isset( $calc['price_per_unit'] ) ? round(floatval( $calc['price_per_unit'] )) : 0;
|
||
$price_old = isset( $calc['price_per_unit_old'] ) ? round(floatval( $calc['price_per_unit_old'] )) : 0;
|
||
$weight = isset( $calc['weight_per_unit'] ) ? floatval( $calc['weight_per_unit'] ) : 0;
|
||
|
||
if ( $price > 0 ) {
|
||
$variation_id = $cart_item['data']->get_id();
|
||
if ( $variation_id ) {
|
||
// Если есть старая цена (скидка), устанавливаем:
|
||
// regular_price = старая цена (зачеркнутая)
|
||
// sale_price = новая цена (финальная)
|
||
if ( $price_old > 0 && $price_old > $price ) {
|
||
update_post_meta( $variation_id, '_regular_price', $price_old );
|
||
update_post_meta( $variation_id, '_sale_price', $price );
|
||
update_post_meta( $variation_id, '_price', $price );
|
||
|
||
$cart_item['data']->set_regular_price( $price_old );
|
||
$cart_item['data']->set_sale_price( $price );
|
||
} else {
|
||
// Нет скидки - обе цены одинаковые
|
||
update_post_meta( $variation_id, '_regular_price', $price );
|
||
update_post_meta( $variation_id, '_sale_price', $price );
|
||
update_post_meta( $variation_id, '_price', $price );
|
||
|
||
$cart_item['data']->set_regular_price( $price );
|
||
$cart_item['data']->set_sale_price( $price );
|
||
}
|
||
}
|
||
|
||
// Устанавливаем финальную цену
|
||
$cart_item['data']->set_price( $price );
|
||
}
|
||
|
||
if ( $weight > 0 ) {
|
||
$cart_item['data']->set_weight( $weight );
|
||
}
|
||
}
|
||
|
||
return $cart_item;
|
||
}
|
||
|
||
/**
|
||
* Восстановление данных калькулятора из сессии
|
||
*/
|
||
add_filter( 'woocommerce_get_cart_item_from_session', 'orgsteklo_get_cart_item_from_session', 10, 3 );
|
||
function orgsteklo_get_cart_item_from_session( $cart_item, $values, $key ) {
|
||
if ( isset( $values['orgsteklo_calculator'] ) ) {
|
||
$cart_item['orgsteklo_calculator'] = $values['orgsteklo_calculator'];
|
||
}
|
||
return $cart_item;
|
||
}
|
||
|
||
/**
|
||
* Устанавливаем цену и вес для вариаций с калькулятором при пересчете корзины
|
||
*/
|
||
add_action( 'woocommerce_before_calculate_totals', 'orgsteklo_set_cart_item_price', 10, 1 );
|
||
function orgsteklo_set_cart_item_price( $cart ) {
|
||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||
return;
|
||
}
|
||
|
||
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
|
||
if ( isset( $cart_item['orgsteklo_calculator'] ) && isset( $cart_item['data'] ) ) {
|
||
$calc = $cart_item['orgsteklo_calculator'];
|
||
|
||
// Округляем до целых для гарантии отсутствия копеек
|
||
$price = isset( $calc['price_per_unit'] ) ? round(floatval( $calc['price_per_unit'] )) : 0;
|
||
$price_old = isset( $calc['price_per_unit_old'] ) ? round(floatval( $calc['price_per_unit_old'] )) : 0;
|
||
$weight = isset( $calc['weight_per_unit'] ) ? floatval( $calc['weight_per_unit'] ) : 0;
|
||
|
||
if ( $price > 0 ) {
|
||
$variation_id = $cart_item['data']->get_id();
|
||
if ( $variation_id ) {
|
||
// Если есть старая цена (скидка), устанавливаем:
|
||
// regular_price = старая цена (зачеркнутая)
|
||
// sale_price = новая цена (финальная)
|
||
if ( $price_old > 0 && $price_old > $price ) {
|
||
update_post_meta( $variation_id, '_regular_price', $price_old );
|
||
update_post_meta( $variation_id, '_sale_price', $price );
|
||
update_post_meta( $variation_id, '_price', $price );
|
||
|
||
$cart_item['data']->set_regular_price( $price_old );
|
||
$cart_item['data']->set_sale_price( $price );
|
||
} else {
|
||
// Нет скидки - обе цены одинаковые
|
||
update_post_meta( $variation_id, '_regular_price', $price );
|
||
update_post_meta( $variation_id, '_sale_price', $price );
|
||
update_post_meta( $variation_id, '_price', $price );
|
||
|
||
$cart_item['data']->set_regular_price( $price );
|
||
$cart_item['data']->set_sale_price( $price );
|
||
}
|
||
}
|
||
|
||
// Устанавливаем финальную цену
|
||
$cart_item['data']->set_price( $price );
|
||
}
|
||
|
||
if ( $weight > 0 ) {
|
||
$cart_item['data']->set_weight( $weight );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Отображение данных калькулятора в корзине
|
||
*/
|
||
add_filter( 'woocommerce_get_item_data', 'orgsteklo_display_cart_item_data', 10, 2 );
|
||
function orgsteklo_display_cart_item_data( $item_data, $cart_item ) {
|
||
if ( ! isset( $cart_item['orgsteklo_calculator'] ) ) {
|
||
return $item_data;
|
||
}
|
||
|
||
$calc = $cart_item['orgsteklo_calculator'];
|
||
|
||
if ( isset( $calc['thickness'] ) ) {
|
||
$item_data[] = [
|
||
'name' => 'Толщина',
|
||
'value' => $calc['thickness'] . ' мм',
|
||
];
|
||
}
|
||
|
||
if ( isset( $calc['width'] ) && isset( $calc['length'] ) ) {
|
||
$item_data[] = [
|
||
'key' => 'Ширина',
|
||
'name' => 'Ширина',
|
||
'value' => $calc['width'] . ' мм',
|
||
'display' => $calc['width'] . ' мм',
|
||
];
|
||
$item_data[] = [
|
||
'key' => 'Длина',
|
||
'name' => 'Длина',
|
||
'value' => $calc['length'] . ' мм',
|
||
'display' => $calc['length'] . ' мм',
|
||
];
|
||
}
|
||
|
||
if ( isset( $calc['weight_per_unit'] ) ) {
|
||
$item_data[] = [
|
||
'name' => 'Вес 1 листа',
|
||
'value' => number_format( (float)$calc['weight_per_unit'], 3, '.', ' ' ) . ' кг',
|
||
];
|
||
}
|
||
|
||
if ( isset( $calc['is_standard_size'] ) && ! $calc['is_standard_size'] ) {
|
||
$item_data[] = [
|
||
'name' => 'Тип размера',
|
||
'value' => 'Нестандартный',
|
||
];
|
||
}
|
||
|
||
return $item_data;
|
||
}
|
||
|
||
/**
|
||
* Вариации больше не нужны - товары стали simple
|
||
*/
|
||
|
||
/**
|
||
* AJAX handler для добавления товара с калькулятором в корзину
|
||
* Использует стандартный add_to_cart() с реальной вариацией
|
||
*/
|
||
add_action( 'wp_ajax_add_orgsteklo_to_cart', 'orgsteklo_ajax_add_to_cart' );
|
||
add_action( 'wp_ajax_nopriv_add_orgsteklo_to_cart', 'orgsteklo_ajax_add_to_cart' );
|
||
|
||
function orgsteklo_ajax_add_to_cart() {
|
||
try {
|
||
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
|
||
$quantity = isset( $_POST['quantity'] ) ? absint( $_POST['quantity'] ) : 1;
|
||
$thickness = isset( $_POST['orgsteklo_thickness'] ) ? floatval( $_POST['orgsteklo_thickness'] ) : 0;
|
||
$width = isset( $_POST['orgsteklo_width'] ) ? floatval( $_POST['orgsteklo_width'] ) : 0;
|
||
$length = isset( $_POST['orgsteklo_length'] ) ? floatval( $_POST['orgsteklo_length'] ) : 0;
|
||
|
||
if ( ! $product_id || ! $thickness || ! $width || ! $length ) {
|
||
throw new Exception( 'Не указаны обязательные параметры' );
|
||
}
|
||
|
||
// Получаем продукт
|
||
$product = wc_get_product( $product_id );
|
||
if ( ! $product ) {
|
||
throw new Exception( 'Товар не найден' );
|
||
}
|
||
|
||
// Находим вариацию по толщине через наше поле
|
||
$variations = get_posts( array(
|
||
'post_type' => 'product_variation',
|
||
'post_parent' => $product_id,
|
||
'numberposts' => -1,
|
||
'fields' => 'ids'
|
||
) );
|
||
|
||
$variation_id = 0;
|
||
foreach ( $variations as $var_id ) {
|
||
$var_thickness = get_post_meta( $var_id, '_orgsteklo_thickness', true );
|
||
if ( abs( floatval( $var_thickness ) - $thickness ) < 0.01 ) {
|
||
$variation_id = $var_id;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ( ! $variation_id ) {
|
||
throw new Exception( 'Не найдена вариация для толщины ' . $thickness . ' мм. Пересохраните товар в админке.' );
|
||
}
|
||
|
||
// Получаем атрибуты вариации
|
||
$variation_obj = wc_get_product( $variation_id );
|
||
$variation_attributes = $variation_obj ? $variation_obj->get_variation_attributes() : array();
|
||
|
||
// Рассчитываем цену из таблицы (НЕ из вариации!)
|
||
$calc_result = orgsteklo_calculate_price( $product_id, $thickness, $width, $length, 1 );
|
||
if ( is_wp_error( $calc_result ) ) {
|
||
throw new Exception( $calc_result->get_error_message() );
|
||
}
|
||
|
||
// Применяем скидку
|
||
$discount_percent = orgsteklo_get_discount_percent_from_tags( $product_id );
|
||
$price_current_sheet = $calc_result['price_current_sheet'];
|
||
$price_current_sheet_old = 0; // Старая цена (до скидки)
|
||
|
||
if ( $discount_percent > 0 ) {
|
||
$price_current_sheet_old = $price_current_sheet; // Сохраняем старую цену
|
||
$coef = (100 - $discount_percent) / 100;
|
||
$price_current_sheet = round( $price_current_sheet * $coef );
|
||
}
|
||
|
||
$is_standard_size = $calc_result['is_standard_size'];
|
||
$weight_current_sheet = $calc_result['weight_current_sheet'];
|
||
|
||
// Данные калькулятора для сохранения
|
||
// Явно округляем цены до целых, чтобы избежать расхождений в копейках
|
||
$calc_data = array(
|
||
'thickness' => $thickness,
|
||
'width' => $width,
|
||
'length' => $length,
|
||
'price_per_unit' => round($price_current_sheet),
|
||
'price_per_unit_old' => $price_current_sheet_old ? round($price_current_sheet_old) : null,
|
||
'weight_per_unit' => $weight_current_sheet,
|
||
'is_standard_size'=> $is_standard_size,
|
||
);
|
||
|
||
// Добавляем в корзину через стандартный метод с найденной вариацией
|
||
$cart_item_key = WC()->cart->add_to_cart(
|
||
$product_id,
|
||
$quantity,
|
||
$variation_id,
|
||
$variation_attributes,
|
||
array( 'orgsteklo_calculator' => $calc_data )
|
||
);
|
||
|
||
if ( ! $cart_item_key ) {
|
||
// Получаем ошибки WooCommerce
|
||
$notices = wc_get_notices( 'error' );
|
||
$error_message = 'Не удалось добавить товар в корзину (variation_id: ' . $variation_id . ')';
|
||
if ( ! empty( $notices ) ) {
|
||
$errors = array();
|
||
foreach ( $notices as $notice ) {
|
||
$errors[] = is_array( $notice ) && isset( $notice['notice'] ) ? $notice['notice'] : $notice;
|
||
}
|
||
$error_message .= ': ' . implode( ', ', $errors );
|
||
}
|
||
wc_clear_notices();
|
||
throw new Exception( $error_message );
|
||
}
|
||
|
||
wp_send_json( array(
|
||
'success' => true,
|
||
'message' => 'Товар успешно добавлен в корзину',
|
||
'cart_item_count' => WC()->cart->get_cart_contents_count(),
|
||
'fragments' => apply_filters( 'woocommerce_add_to_cart_fragments', array() ),
|
||
'cart_hash' => WC()->cart->get_cart_hash()
|
||
) );
|
||
|
||
} catch ( Exception $e ) {
|
||
wp_send_json_error( $e->getMessage() );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Сохранение данных калькулятора в заказе
|
||
*/
|
||
add_action( 'woocommerce_checkout_create_order_line_item', 'orgsteklo_add_calculator_to_order', 10, 4 );
|
||
function orgsteklo_add_calculator_to_order( $item, $cart_item_key, $values, $order ) {
|
||
if ( ! isset( $values['orgsteklo_calculator'] ) ) {
|
||
return;
|
||
}
|
||
|
||
$calc = $values['orgsteklo_calculator'];
|
||
|
||
if ( isset( $calc['thickness'] ) ) {
|
||
$item->add_meta_data( 'Толщина', $calc['thickness'] . ' мм' );
|
||
}
|
||
|
||
if ( isset( $calc['width'] ) && isset( $calc['length'] ) ) {
|
||
$item->add_meta_data( 'Размер', $calc['width'] . ' × ' . $calc['length'] . ' мм' );
|
||
}
|
||
|
||
if ( isset( $calc['weight_per_unit'] ) ) {
|
||
$item->add_meta_data( 'Вес 1 листа', number_format( $calc['weight_per_unit'], 3, '.', '' ) . ' кг' );
|
||
}
|
||
|
||
if ( isset( $calc['is_standard_size'] ) && ! $calc['is_standard_size'] ) {
|
||
$item->add_meta_data( 'Тип', 'Нестандартный размер' );
|
||
}
|
||
}
|