WooCommerce calculator

Het komt regelmatig voor dat ik wordt gevraagd voor het maken van een WooCommerce calculator. Er zijn vele mogelijkheden maar hier gaat het voornamelijk om het probleem van verkoop per m2 maar uiteindelijk moet men het per pak verkopen. Denk bijvoorbeeld aan karpet, vloer- en wandtegels maar kan bijvoorbeeld ook voor verf gebruikt worden. Waar men de benodigde hoeveelheid berekend op basis van oppervlakte maar uiteindelijk moet berekenen hoeveel pakken, potten, blikken etc. nodig zijn. Ook kan je hierin een verlies verwerken.

Meer kan men lezen over dit project in Project WooCommerce Calculator.

Hier zullen we de volgende onderdelen bespreken:
Gebruik de links om direct naar het onderwerp te gaan.

Dit jaar heb ik een dergelijke calculator gebouwd voor David Rusman van Dwork. Deze wanbetaler besloot mij uiteindelijk niet te betalen voor mijn werk met het verhaal dat iemand anders de code had geschreven. Hiermee komt dus de white label status te vervallen en blijf ik eigenaar en auteur van de code. En in dit geval wil ik deze code delen met iedereen zodat iedereen hier gebruik van kan maken.
Meer informatie over David Rusman op businessinsider of download de pdf hier.

Aan het eind kan men het child-theme downloaden voor wie hierin is geïnteresseerd.

Met een WordPress Onderhoudspakket kunt u zorgeloos ondernemen.

WordPress Onderhoudspakket

Waarom een WooCommerce calculator?

Voornamelijk bedrijven die wandtegels, vloertegels en karpet verkopen willen een dergelijke calculator gebruiken. Het probleem is namelijk dat tegels en parket in pakken worden verkocht maar de klanten willen in m2 kopen. Men weet vaak wat het oppervlak is en gebruiken hierbij ook nog eens een snijverlies. Dit is algemeen bekend bij dergelijke bedrijven, tegelzetters en dergelijken.

In deze berekening bepaald de verkoper de prijs per m2 en het totaal oppervlak in m2 van het pakket. De klant kan de totaal benodigde oppervlakte invoeren en een snijverlies selecteren. Men kan ook het aantalle pakken kiezen. Belangrijk is dat het de klant makkelijker maakt.

Woocommerce Calculator - Freelance Webshop ontwikkeling

Dit is voor veel bedrijven een tegemoetkoming aan de klanten om te helpen met de berekeningen. De klant hoeft niet meer te berekenen hoeveel pakken die nodig heeft. Men kan nu gewoon de benodigde oppervlakte invoeren met snijverlies en krijgt het totaal aantal pakken dat hij nodig heeft en hoeveel m2 dit bij elkaar is en de totaal prijs hiervoor.

En als dit het de klant makkelijker maakt om te bestellen dan is het ook goed voor verkoop. Een win win voor de verkoper en de koper.

Demo WooCommerce calculator

Een demo van deze WooCommerce calculator kan men bekijken op Demo WooCommerce Calculator

De demo is puur fictief en dient alleen als demo en hier kunnen geen rechten aan ontleed worden. In deze demo kan men het totaal benodigde oppervlakte opgeven en het gewenste snijverlies selecteren. Hiermee zal het totaal aantal benodigde pakken berekend worden. Ook wordt het totaal oppervlakte en de prijs weergegeven van dit totaal aantal pakken.

Voor de demo is gebruik gemaakt van een plugin dus kan enigszins afwijken van de code die hier wordt gegeven. De basis is hetzelfde maar enkele aanpassingen waren nodig om er een plugin van te maken en voor het design van de demo.

Code WooCommerce calculator

Voor nu gaan we bekijken hoe deze code is opgebouwd zodat iedereen dit kan aanpassen naar zijn eisen en wensen.
De code die we hier behandelen kan je plaatsen in de functions.php van je huidige thema maar beter is deze te plaatsen in een child-theme. Ook is het mogelijk er een plugin van te maken.

Ten eerste maken we een nieuw custom field aan voor WooCommerce voor de producten. In dit geval de oppervlakte per pak. Deze gaan we later hergebruiken om de rest van de code te plaatsen op de betreffende pagina’s waar de calculator getoond dient te worden.


add_action('woocommerce_product_options_general_product_data', 'woocommerce_product_custom_area_field');
function woocommerce_product_custom_area_field() {
	global $woocommerce, $post;
	echo '<div class="product_calculator_field">';
	// Custom Product Area Field
	woocommerce_wp_text_input(
	    array(
	        'id' => 'custom_product_area',
	        'placeholder' => '1',
	        'label' => __('Total Package Area (m<sup>2</sup>)', 'wccalculator'),
	        'type' => 'number',
	        'description' => __('Product package area in m<sup>2</sup> to use in the calculator.', 'wccalculator'),
	        'custom_attributes' => array(
		    'step' => 'any',
		    'min' => '0',
	        ),
	    )
	);
 
    echo '</div>';
}
 
// Save Custom WooCommerce Field Area
add_action('woocommerce_process_product_meta', 'woocommerce_product_custom_area_field_save');
function woocommerce_product_custom_area_field_save($post_id) {
    // Custom Product Area Field
    $woocommerce_custom_product_area_field = $_POST['custom_product_area'];
    if(!empty($woocommerce_custom_product_area_field)) {
        update_post_meta($post_id, 'custom_product_area', esc_attr($woocommerce_custom_product_area_field));
    }
}

De eerste functie is voor het aanmaken van het custom field en de tweede is om deze op te slaan. Nu is het nieuw veld te zien als we een product aanmaken en kunnen we deze opslaan in de database.

Met de volgende code bouwen we de HTML op voor de calculator maar eerst controleren we of er een oppervlakte is ingevuld voor een pak. Zo niet dan is de WooCommerce Calculator niet nodig. Deze HTML toont een veld voor benodigde oppervlakte, een select voor snijverlies, totaal oppervlakte van het totaal benodigde aantal pakken. Als laatst de totale prijs van het aantal pakken samen. Het aantal pakken wordt getoond als aantal en hiervoor wordt standaard WooCommerce voor gebruikt.


// Build up the HTML for the Calculator
add_action('woocommerce_before_add_to_cart_button', 'clw_product_add_on', 9);
function clw_product_add_on() {
    global $product;
    $area = get_post_meta($product->get_id(), 'custom_product_area', true);
    if($area && !empty($area)) {
        $clw_price = $product->get_price();
        $total = $clw_price * $area;
        $price = number_format($total, 2);
        if($price && !empty($price)) {
            $value = isset($_POST['area_needed']) ? sanitize_text_field($_POST['area_needed']): $area;
            echo '<div class="oppervlakte-calculator">
                <div class="calculator-item item-aantal">
                    <label>Aantal (m<sup>2</sup>)</label>
                    <input name="area_needed" class="calculator-aantal" type="text" value="'.$value.'"> m<sup>2</sup>
                </div>
                <div class="calculator-item item-snijverlies">
                    <label>Snijverlies</label>
                    <select class="calculator-snij" name="snijverlies">
                        <option value="1" selected>0%</option>
                        <option value="1.1">10%</option>
                        <option value="1.15">15%</option>
                    </select>
                </div>
                <div class="calculator-item item-pakinhoud">
                    <label>Pakinhoud (m<sup>2</sup>)</label>
                    <input name="item_pakinhoud" type="text" class="calculator-pakinhoud" value="'.$value.'" disabled="disabled"> m<sup>2</sup>
                </div>
                <div class="calculator-item item-totaal">
                    <label>Totaalprijs</label>
                    <input name="item_totaal" class="calculator-totaal" type="text" value="'.$price.'" disabled="disabled"> €
                </div>
            </div>';
        }
   }
}

Dit zal er ongeveer als volgt uit kunnen zien maar is zoals het te zien is op de demo.

Woocommerce Calculator - Freelance Webshop ontwikkeling

Van deze invoervelden zijn er twee die we ook gaan tonen in de winkelmand, de order en de e-mail. Het gaat hier om “area_needed” en “snijverlies”. Voordat we dat gaan doen beginnen we met de validatie van “area_needed”. We moeten namelijk controleren ofdat men een gewenste oppervlakte heeft ingevuld voor hij kan bestellen.


// Validation and errorhandling Area Needed when add to cart
add_filter('woocommerce_add_to_cart_validation', 'clw_product_add_on_validation', 10, 3);
function clw_product_add_on_validation($passed, $product_id, $qty){
    if(isset($_POST['area_needed']) && sanitize_text_field($_POST['area_needed']) == '') {
        wc_add_notice( 'Aantal(m<sup>2</sup>) is verplicht', 'error');
        $passed = false;
    }
    return $passed;
}

Nu gaan we eerst “area_needed” en “snijverlies” toevoegen aan de Cart Item Data.


// Add Area Needed to the Cart Item Data
add_filter('woocommerce_add_cart_item_data', 'clw_product_add_on_cart_item_data', 10, 2);
function clw_product_add_on_cart_item_data($cart_item, $product_id){
    if(isset($_POST['area_needed'])) {
        $cart_item['area_needed'] = sanitize_text_field($_POST['area_needed']);
    }
    return $cart_item;
}
 
// Add Cutting Loss to the Cart Item Data
add_filter('woocommerce_add_cart_item_data', 'clw_product_add_on_cart_item_snij', 10, 2);
function clw_product_add_on_cart_item_snij($cart_item, $product_id){
    if(isset($_POST['snijverlies'])) {
        $cart_item['snijverlies'] = sanitize_text_field($_POST['snijverlies']);
    }
    return $cart_item;
}

En nu gaan we bepalen hoe “area_needed” en “snijverlies” getoond dient te worden bij het product in de winkelmand.


// Add Area Needed to the cart item data
add_filter('woocommerce_get_item_data', 'clw_product_add_on_display_cart', 10, 2);
function clw_product_add_on_display_cart($data, $cart_item) {
    if(isset($cart_item['area_needed'])){
        $data[] = array(
            'name' => 'Aantal(m<sup>2</sup>)',
            'value' => sanitize_text_field($cart_item['area_needed'])
        );
    }
    return $data;
}
 
// Add Cutting Loss to the cart item data
add_filter('woocommerce_get_item_data', 'clw_product_snijverlies_display_cart', 10, 2);
function clw_product_snijverlies_display_cart($data, $cart_item) {
    if(isset($cart_item['snijverlies'])){
        $snij = $cart_item['snijverlies'];
        if($snij == 1.1) {
            $data[] = array(
                'name' => 'Snijverlies',
                'value' => '10%'
            );
        } elseif($snij == 1.15) {
            $data[] = array(
                'name' => 'Snijverlies',
                'value' => '15%'
            );
        } else {
            exit;
        }
    }
    return $data;
}

En nu voegen we de “area_needed” en “snijverlies” toe aan de Order Item Meta. Zodat we het later kunnen tonen in de order en in WordPress backend. Dit is nodig om deze gegevens bij het product te tonen in de order en de order e-mail.


// Add Area Needed to Order Item Meta
add_action('woocommerce_add_order_item_meta', 'clw_product_add_on_order_item_meta', 10, 2);
function clw_product_add_on_order_item_meta($item_id, $values) {
    if(!empty($values['area_needed'])) {
        wc_add_order_item_meta($item_id, 'Aantal(m<sup>2</sup>)', $values['area_needed'], true);
    }
}
 
// Add Cutting Loss to Order Item Meta
add_action('woocommerce_add_order_item_meta', 'clw_product_snijverlies_order_item_meta', 10, 2);
function clw_product_snijverlies_order_item_meta($item_id, $values) {
    if(!empty($values['snijverlies']) && $values['snijverlies'] == '1.1') {
        wc_add_order_item_meta( $item_id, 'Snijverlies', '10%', true);
    } elseif(!empty($values['snijverlies']) && $values['snijverlies'] == '1.15') {
        wc_add_order_item_meta($item_id, 'Snijverlies', '15%', true);
    }
}

Nu gaan we “area_needed” en “snijverlies” toevoegen aan de order.


// Add Area Needed to Order
add_filter('woocommerce_order_item_product', 'clw_product_add_on_display_order', 10, 2);
function clw_product_add_on_display_order($cart_item, $order_item){
    if(isset($order_item['area_needed'])){
        $cart_item['area_needed'] = $order_item['area_needed'];
    }
    return $cart_item;
}
 
// Add Cutting Loss to Order
add_filter('woocommerce_order_item_product', 'clw_product_snijverlies_display_order', 10, 2);
function clw_product_snijverlies_display_order($cart_item, $order_item){
    if(isset($order_item['snijverlies']) && ($order_item['snijverlies'] == '1.1' || $order_item['snijverlies'] == '1.15')){
        if($order_item['snijverlies'] == '1.1') {
            $cart_item['snijverlies'] = '10%';
        } elseif($order_item['snijverlies'] == '1.15') {
            $cart_item['snijverlies'] = '15%';
        }
        return $cart_item;
    }
}

Uitindelijk zullen we als laatst de “area_needed” en “snijverlies” toevoegen aan de order e-mail.


// Add Area Needed to Order E-mail
add_filter('woocommerce_email_order_meta_fields', 'clw_product_add_on_display_emails');
function clw_product_add_on_display_emails($fields) {
    $fields['area_needed'] = 'Aantal(m<sup>2</sup>)';
    return $fields;
}
 
// Add Cutting Loss to Order E-mail
add_filter('woocommerce_email_order_meta_fields', 'clw_product_snijverlies_display_emails');
function clw_product_snijverlies_display_emails($fields, $order_obj) {
    $snij = get_post_meta($order_obj->get_order_number(), 'snijverlies', true);
 
    if($snij == '1.1' || $snij == '1.15') {
        $fields['area_needed'] = 'Snijverlies';
        return $fields;
    }
}

Nu hebben we uiteindelijk een nieuw veld aangemaakt en deze gebruiken we als een waarde maar ook om te controleren of de WooCommerce Calculator getoond moet worden of niet. De waarden voor “area_needed” en “snijverlies” hebben we nu ook volledig verwerkt voor de backend, de winkelmand, de order en de order e-mail.
Maar we zijn nog niet klaar want nu klomt het belangrijkste gedeelte. We gaan nu het script en css toevoegen aan de calculator.

Vraag vrijblijvende een offerte aan.

Offert Aanvragen

Ook hier wordt weer bepaald of het script geladen moet worden of niet. Hiervoor wordt weer opnieuw het oppervlakte per pak(custom_product_area) gebruikt. Als dit leeg is dan zal er niets getoond worden.


// Add Calculator Script to the Product Page
add_action('woocommerce_after_add_to_cart_button', 'clw_product_script', 9);
function clw_product_script() {
    global $product;
 
    $area = get_post_meta($product->get_id(), 'custom_product_area', true);
    if($area && !empty($area)) {
        $clw_price = $product->get_price();
        $price = number_format($clw_price, 2);
        $pakket = str_replace(',','.',$area);
        $pakket = number_format($pakket, 2);
        $pakprijs = str_replace(',','.',$clw_price);
        $pakprijs = number_format($pakprijs, 2);
        $pakprijs = $pakprijs * $pakket;
        if($pakprijs && !empty($pakprijs)) {
        echo '<script type="text/javascript" charset="utf-8">';
        echo "jQuery(document).ready(function() {
            jQuery('input.calculator-aantal').on('keyup change', function(m) {
                var snij = jQuery('select.calculator-snij').val();
                var pa = jQuery('.calculator-aantal').val();
                var pak = pa * snij;
                var pakken = pak / $pakket;
                var pack = Math.ceil(pakken);
                var totalprice = pack * $pakprijs;
                var totprice = totalprice.toFixed(2);
                var tprice = totprice.replace('.', ',');
                var pInhoud = Math.ceil(pakken) * $pakket;
                var totinhoud = pInhoud.toFixed(2);
                var pakinhoud = totinhoud.replace('.', ',');
                jQuery('input[name=quantity]').val(Math.ceil(pakken));
                jQuery('input.calculator-totaal').val(tprice);
                jQuery('input.calculator-pakinhoud').val(pakinhoud);
            });
            jQuery('select.calculator-snij').on('keyup change', function(s) {
                var snij = jQuery('select.calculator-snij').val();
                var pa = jQuery('.calculator-aantal').val();
                var pak = pa * snij;
                var pakken = pak / $pakket;
                var pack = Math.ceil(pakken);
                var totalprice = pack * $pakprijs;
                var totprice = totalprice.toFixed(2);
                var tprice = totprice.replace('.', ',');
                var pInhoud = Math.ceil(pakken) * $pakket;
                var totinhoud = pInhoud.toFixed(2);
                var pakinhoud = totinhoud.replace('.', ',');
                jQuery('input[name=quantity]').val(Math.ceil(pakken));
                jQuery('input.calculator-totaal').val(tprice);
                jQuery('input.calculator-pakinhoud').val(pakinhoud);
            });
            jQuery('input.qty').on('keyup change', function(a) {
                var opper = a.target.value;
                var snij = jQuery('select.calculator-snij').val();
                var opp = opper * $pakket;
                var meter = opp.toFixed(3);
                var meters = meter.replace('.', ',');
                var meterprice = opper * $pakprijs;
                var mtrprice = meterprice.toFixed(2);
                var mprice = mtrprice.replace('.', ',');
                var pInhoud = opper * $pakket;
                var pakketInhoud = pInhoud.toFixed(2);
                var inhoudPak = pakketInhoud.replace('.', ',');
                jQuery('input.calculator-aantal').val(meters);
                jQuery('input.calculator-totaal').val(mprice);
                jQuery('input.calculator-pakinhoud').val(inhoudPak);
            });
            });
            </script>";
            echo '<style id="woocommerce-calculator-css" type="text/css">
                div.oppervlakte-calculator{width:auto;display:block;max-width:400px;padding:15px;background-color:#fff;border:1px solid #4676bb;margin-bottom:20px}
                div.oppervlakte-calculator .calculator-item{margin-bottom:8px}
                div.oppervlakte-calculator label, div.oppervlakte-calculator input[type="text"] {display:inline-block;vertical-align:baseline;}
                div.oppervlakte-calculator label {width:170px}
                div.oppervlakte-calculator input[type="text"], div.oppervlakte-calculator select {width:100px;margin-left:calc(100% - 300px)}
                div.oppervlakte-calculator input[type="text"]{background-color:#fff;border:1px solid #4676bb;box-shadow:0 0 0 transparent;padding:4px;text-align:right}
                div.oppervlakte-calculator select{background-color:#fff;box-shadow:0 0 0 transparent;border:1px solid #4676bb;padding:4px}
                div.oppervlakte-calculator .item-pakinhoud input[type="text"], div.oppervlakte-calculator .item-totaal input[type="text"] {border:0 none;box-shadow:0 0 0 transparent;background-color:#f2f2f2}
            </style>';
        }
    }
}

Hier is het misschien handig een korte uitleg te geven over het script.
In het eerte gedeelte worden verschillende data verzameld en decimalen worden van komma naar punt veranderd, dit is nodig voor de berekeningen. Daarna zijn er 3 functies. De eerste is voor als de bezoeker de benodige oppervlakte veranderd. De tweede voor als de bezoeker het snijverlies veranderd. En de laatste is voor als de bezoeker het aantal pakken aanpast. Zo zijn er drie mogelijkheden om met de WooCommerce Calculator te werken.

De CSS kan men aanpassen zoals men wilt, dat zal waarschijnlijk voor zich spreken.

Nu zijn we nog steeds niet klaar want als men bij een product een oppervlakte invuld voor een pak en een prijs per m2 invult dan moet dit ook getoond worden op de website. Hiervoor is het volgende stukje code.
Ook hier wordt weer eerst gekeken of er een oppervlakte per pak is ingevuld voor het product.


// Add price suffix to product price
add_filter('woocommerce_get_price_html', 'wcc_price_html', 100, 2);
function wcc_price_html($price, $product){
    global $product;
 
    $area = get_post_meta($product->get_id(), 'custom_product_area', true);
    if($area && !empty($area)) {
        $suffix = '<span class="price-suffix">per m<sup>2</sup></span>';
        $price = $price . ' ' . $suffix;
 
        return $price;
    }
}

Met deze code wordt er bij elk product, waar het oppervlak per pak is ingevuld “per m2” achter de prijs getoond. Op de productpagina maar ook op de categorie pagina en alle andere pagina’s waar het product word getoond.

WooCommerce calculator downloaden

Voor de demo is een plugin gebruikt. Deze plugin is niet gratis te downloaden.
Voor wie geinteresseerd is in de officiele versie zoals deze is gebouwd voor David Rusman, van Dwork, die niet wilde betalen, is hier het child-theme voor het flatsome thema.

Dit child-theme kan men aanpassen zoals men wenst en mag overal voor gebruikt worden. De code is open source maar hier kunnen geen rechten aan ontleend worden en wij nemen geen enkele verantwoordelijkheid.

Toepassingen en gebruik

Dit is slechts een WooCommerce calculator voor een bepaald doel maar uiteraard zijn er allerlei soorten berekeningen mogelijk voor verschillende soorten producten. De mogelijkheden zijn eindeloos maar hier kunnen we niet alle mogelijkheden bespreken. Er is speciaal voor dit type WooCommerce calculator gekozen omdat dit een bekend probleem is waar geen plugin voor bestaat die voldoet aan alle eisen. Dit is waarschijnlijk ook omdat elk product weer zijn eigen eisen en wensen heeft.

Deze calculator kan gebruikt worden voor verschillende producten. Denk bijvoorbeeld ook aan verf dat je ook niet koopt per m2. Maar het zou ook gebruikt kunnen worden voor tapijt, textiel en nog veel meer.

Andere belangrijke reden om deze code hier te delen is omdat de persoon voor wie ik dit heb gemaakt, wanbetalter David Rusman van Dwork, mij niet heeft betaald. Vermoedelijk wil hij hier een plugin van maken en misschien doorverkopen of wat dan ook. Het leek dan ook de meest logische reactie om deze code te delen met iedereen.

De code kan gebruikt worden zoals hier getoond wordt en kan aangepast worden voor veel producten waar men uitgaat van een benodige oppervlakte maar waar de prijzen per pak, pot of andere verpakking zijn. De code is gratis maar Cornelis de Leeuw van Weenen is de auteur en heeft dus alle auteursrechten op dit child-theme maar is niet verantwoordelijk voor het gebruik hiervan.

Er zijn nog geen reacties. Wees de eerste..!!

Reageer

E-mailadres wordt niet gepubliceerd.
Verplichte velden zijn gemarkeerd met *

*
*
*