Opencart + unique product with options + stock control - unique

I make bracelets and place them in my opencart store. Each bracelet is unique, so I have 1 in stock. But customer has to let me know the size of his/her wrist so I can adapt it.
Options ask me for quantities. So, I can not use them because I must enter a number or the option does not become visible.
What I need is:
bracelet B ---> tell me your wrist´s size: (here a drop down or a text box to let the customer choose or write).
Order will reads: Bracelet B... Size: 18cm.... xx $
Then, when the customer pays, Bracelt B is out of stock.
Now, I can do all that, but any time a customer adds an option, the bracelet keeps available.
So Order reads:
Bracelet B ... Size: 18cm... xx$
Bracelet B ... Size: 19cm... xx$ etc
function addToCart(product_id, quantity) {
quantity = typeof(quantity) != 'undefined' ? quantity : 1;
$.ajax({
url: 'index.php?route=checkout/cart/add',
type: 'post',
data: 'product_id=' + product_id + '&quantity=' + quantity,
dataType: 'json',
success: function(json) {
$('.success, .warning, .attention, .information, .error').remove();
if (json['redirect']) {
location = json['redirect'];
}
if (json['success']) {
$('#notification').html('<div class="success" style="display: none;">' + json['success'] + '<img src="catalog/view/theme/default/image/close.png" alt="" class="close" /></div>');
$('.success').fadeIn('slow');
$('#cart-total').html(json['total']);
$('html, body').animate({ scrollTop: 0 }, 'slow');
}
/*adding Shadyyx solution*/
if (json['error']) {
$('#notification').html('<div class="error" style="display: none;">' + json['error'] + '<img src="catalog/view/theme/default/image/close.png" alt="" class="close" /></div>');
$('html, body').animate({ scrollTop: 0 }, 'slow');
$('.error').fadeIn('slow');
}
/*end adding*/
}
});
}

In default OC You have the ability to disable ordering of out of stock products.
Simply go to the administration -> System -> Settings, click edit on Your store and navigate to the Options tab. Then scroll down to the Stock section and notice the Stock Checkout: option. If You select No and save the users won't be able to order the products that are not in stock.
This means that if You create a bracelet product with 1 piece in stock, add an option to it with one piece in stock and somebody order this one, it's stock will be immediately set to Not in stock and nobody will be able to order it again.
If You'd like to hide all the products that are not in stock after they are ordered, You have two options - either do this manually by disabling the product or You'd need to implement few modifications in to the getProducts() method for the product model to load only those products that are still in stock.
UPDATE regarding the comment: You are misunderstaning the options in OpenCart. One option for which You have the stock 1 piece is the size option, which may have different values, e.g. 15cm, 16cm, 17cm, 18cm, etc. All these values contained in one single size option for one single stock item mean that if I select any of them, after ordering the bracelet there will no more pieces left.
What You are telling is creating one option for 15cm with 1pcs stock, another option for 16cm with 1pcs stock, etc., thus having 1 piece in stock for each size - this is incorrect (i.e. misuse of product options). Nevertheless, even in this case, when different sizes have all one piece but the product itself has only one piece, after ordering first it should be out of the stock even there are options with stock left...
Step by step walkthrough:
Go to Your OC administration and log in.
Hover the mouse over the Catalog menu point and click on the Options entry
On this Options overview screen notice the Insert in the top-right corner - click it
Enter the Option's name, e.g. Wrist Size
Choose the Option's type, either Select or Radio (depending on how many possible values You want to have, more than 4, use Select)
Sort Order may be a numeric value (or when blank, will be filled with 0)
Now there is empty table underneath with one row containing only a button Add Option Value - by clicking this it will add a row with empty form fields to enter the Option's value; let's say we want to add wrist sizes from 15cm to 22cm => 8 values => click that button 8 times (be careful, after each click it will be moved downward as a new row with form fields will appear above it)
into that 8 rows enter all the necessary values, e.g. 15cm, 16cm, ..., 22cm as value's name and sort order to match Your needs (leaving blank may lead to inappropriately ordered values when displayed)
Click on Save button located at top-right corner.
Now navigate Yourself to the products overview, select the product You want to add this option to and click Edit in that row. Move to the Data tab and make sure the bracelet has these settings:
Quantity: 1
Subtract Stock: Yes
Out of Stock Status: Out of Stock
Then move to the Option tab and add the new option by typing the Option's name (Wrist Size) in the left area - after the Option is found, click on that label and a new Option (new tab) will be added to the view. Now make sure the Option is required and add all possible values while setting these settings to all of them:
Quantity: 1
Subtract Stock: Yes
The other option values depends on Your business model. Now Save the product and try to order it with any of the wrist sizes value. If You have the settings set for the store that the customer isn't possible to order the products that are out of stock, this should work for You.
Let me know if there is something that I missed (or if it still doesn't work).
UPDATE:
Here is one possible solution (not tested but I believe it will work out of the box or maybe there is only simple bug You may fix Yourself):
Open up the catalog/controller/checkout/cart.php and find this line (should be at 543):
$this->cart->add($this->request->post['product_id'], $quantity, $option);
and before this line directly add this code (You may want to do this via vQmod extension):
if ($product_info['quantity'] == 1 && $product_info['subtract'] == 1) {
$products = $this->cart->getProducts();
$already_added = false;
foreach ($products as $product) {
if ($product['product_id'] == $this->request->post['product_id']) {
$already_added = true;
break;
}
}
if ($already_added) {
return $this->response->setOutput(json_encode(array(
'error' => $this->language->get('text_product_already_added')
)));
}
}
Then open up this file catalog/language/english/checkout/cart.php and add this to the end:
$_['text_product_already_added'] = 'This product has allowed quantity of 1 piece and is already added to a cart. Remove it from the cart to be able to add it (e.g. with different size).';
This is all only as an example, You may edit the error message to meet Your requirements.
Warning: this is only a simple solution not letting the same user (or within the same session) to add the same product twice or more times into the cart but it won't prevent the same product being added and ordered at the very same time by two different users (or one user using two browsers, for example). For this edge case You'd need to implement some kind of product locking - after it is added to cart this is saved to a DB and nobody else would be able to add the same product into the cart. In this case it would be nice to store also the datetime when it was locked and have a cron job that will unlock this product (also with removing from the cart) so that the product is not locked for ever and is orderable by other users again...
EDIT for JS part:
Open up this file catalog/view/javascript/common.js and search for method function addToCart(product_id, quantity) { - in this file find this part:
if (json['success']) {
...
}
and after this one add this code:
if (json['error']) {
$('#notification').html('<div class="error" style="display: none;">' + json['error'] + '<img src="catalog/view/theme/default/image/close.png" alt="" class="close" /></div>');
$('html, body').animate({ scrollTop: 0 }, 'slow');
$('.error').fadeIn('slow');
}
This should be enough.
UPDATE XYZ:
In PHP find this code what have we added:
return $this->response->setOutput(json_encode(array(
'error' => $this->language->get('text_product_already_added')
)));
and change it to this (then try):
$this->response->setOutput(json_encode(array(
'error' => $this->language->get('text_product_already_added')
)));
return;
The point is to see in console where the request is done to index.php?route=checkout/cart/add the response with either success or error message in response. Try for both cases to make sure You are looking at the correct request (in success You can see the success message on the top of page so You may be sure it was done) and then try again to receive error (for the same product) message - it should be contained in the response the same way as the success message is. If still doesn't work, try to change return; to exit;...

Unfortunately I can't comment your post.
I had the problem with error message not showing up, when trying to add the product twice.
I needed to add the JS code to /catalog/view/theme/*/product/product.tpl
Just search for "url: 'index.php?route=checkout/cart/add'" and add the code shadyxx postet right after
if (json['success']) { ... }

Related

Using DataTables how to display a running total of an amount entered in each row?

http://live.datatables.net/dalogaci/1/edit
I have an amount of money to be dispersed and am using DataTables to display a list of people and allow entry of an amount next to each person (their share of the disbursement). I want to provide a running total of the amount entered into the table so I can warn when the total to be dispersed has been reached or passed.
Kind regards,
Glyn
You can use the following approach.
In my case, I display the running total in a <div>, rather than an input box, as the value is only for display purposes:
<div id="showsum">Grand Total: $0.00</div>
The end result:
The script for this - which I have tried to explain with comments in the code:
<script type="text/javascript">
// define the table variable here so the doSum()
// function will have access to it, when needed:
var table;
// reads each value from the final column in the table, checks
// if the value is a number (as opposed to blank), and then
// keeps a running total. Ensure we round fractions of pennies
// as needed.
//
// When handling money, use a big number library - see this:
// https://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript
//
function doSum() {
//var foop = table.columns(5).nodes().to$();
var sum = 0.0;
// this gets each node (cell) in the final column:
table.columns(5).nodes().to$()[0].forEach(function (item) {
// see if the display value is a number (i.e. not blank):
var amt = parseFloat($('input', item ).val());
if (!isNaN(amt)) {
sum += amt;
}
});
// round and display to 2 decimal places:
sum = (Math.round((sum + Number.EPSILON) * 100) / 100).toFixed(2);
$('#showsum').text("Grand Total: $" + sum);
}
$(document).ready(function() {
table = $('#example').DataTable( {
"columnDefs": [ {
"targets": 5,
"data": function ( row, type, val, meta ) {
// note the use of onchange="doSum()" in the following:
return '<input type="number" min="0" max="99999.99" step=".01" placeholder="0.00" onchange="doSum()">';
}
} ]
} );
} );
</script>
For a change to be added to the grand total, you have to hit "enter", or click outside of the input field, if you type the value in manually.
Because you are dealing with money, the code should really be using a "big number" format to eliminate the risk of inaccuracies in fractions of pennies (due to limitations in floating point arithmetic). For example see here.
Final note: I see this question was down-voted. I think that may have been because you only link to your demo code, instead of showing the relevant parts in the question itself. The link to the demo is useful - but showing code in the question itself is generally a "must-do", I think.

Angular 2 - Disable form control based on the value of another form control

I need help in disabling the value of a specific form control based on its value in Angular 2.
The field - startDate needs to be disabled whenever the value of another field - period is "Lifetime". It needs to be enabled otherwise. Also, whenever the app is loaded, the default value of period is "Lifetime" so the startDate is disabled when the form is loaded.
So this is the code that I have written till now in my component:
protected onMetadataLoaded() {
const startDateControl = this.metadata.form.controls.startDate;
startDateControl.disable();
const periodControl = this.metadata.form.controls.period;
periodControl.valueChanges
.distinctUntilChanged()
.subscribe(period => {
if(period !== "Lifetime") {
startDateControl.enable();
} else {
startDateControl.disable();
}
});
}
This allows me to enable the startDateControl when I change the period value from Lifetime to any other value. However, if I select the period value as Lifetime again, the startDateControl doesn't get disabled.
I'm guessing that it's checking it only once since I'm putting the code inside MetadataLoaded(). I'm not sure as to how I need to go about it so that it keeps monitoring the periodControl for value changes and enables and disables the startDateControl based on the period value.
Just for completeness: Period field has the following values - Monthly, Quarterly, Yearly, Lifetime.
Thanks in advance!
EDIT:
So this is what my template looks like:
<dynamic-form-item name="storeID" [metadata] = "metadata" type="chooser"></dynamic-form-item>
<dynamic-form-item name="period" [metadata] = "metadata"></dynamic-form-item>
<dynamic-form-item name="startDate" [metadata] = "metadata"></dynamic-form-item>
Basically a bunch of dynamic form items.

Value Calculation issue in Google web HTML App

I have created an HTML web app in google script this works like a calculator, This app works fine if I add the input in descending order however if I skip the order and update in put data numbers randomly in any column then I am not getting the output properly
Example:- update the numbers in box number 4 and 5 then update in box number 1 you will find the differences in total numbers
Please refer the attached sheet for detailed script
Project Name- Project Proposal Form
$("#rTpe1").keyup(function(e){
$("#rFor1").val(this.value * $("#PerHourRate1").val());
$("#rFor3").val( Number($("#rFor1").val()) +Number($("#rFor2").val()))
});
$("#rTpe2").keyup(function(e){
$("#rFor2").val(this.value * $("#PerHourRate2").val());
$("#rFor3").val( Number($("#rFor1").val()) + Number($("#rFor2").val()))
});
$("#rTpe12").keyup(function(e){
$("#rFor12").val(this.value * $("#PerHourRate3").val());
$("#rFor3").val( Number($("#rFor1").val()) + Number($("#rFor2").val())+ Number($("#rFor12").val()))
});
$("#rTpe13").keyup(function(e){
$("#rFor13").val(this.value * $("#PerHourRate4").val());
$("#rFor3").val( Number($("#rFor1").val()) + Number($("#rFor2").val())+ Number($("#rFor12").val())+ Number($("#rFor13").val()))
});
I could be wrong, but I think that's the main culprit:
If your work your way top to bottom, the output in '#rFor3' is not affected. For example, if you enter values in the first field ('#rTpe1'), this statement
Number($("#rFor2").val()))
will evaluate to '0' because '#rFor2' probably contains an empty string at this point and Number("") will get you a zero. Because all subsequent input fields reference the results of previous calculations ('rTpe2' references 'rFor1', 'rTpe12' references both 'rFor1' and 'rFor2', etc), the sum will come out as correct.
Now consider the reverse scenario. For simplicity, let's make all your rates equal to 1. If you enter the value of '5' into 'rTpe12', the value of 'rFor3' will be
Number("") + Number("") + Number(5*1) == 5; //the first two inputs will contain empty strings at this point
The output of '#rFor3' would be 5. If you go up a step and enter the value of '2' into 'rTpe2', the value of the 'rFor3' output will change to
Number("") + Number(2*1) == 2; the first input will contain an empty string.
The code is not easy to understand, so even if this solution doesn't work for you, consider caching your DOM elements to improve performance and make your code more readable. Currently, you are using jQuery selectors to search the DOM over and over again, which is a serious performance drag. You could also store your calculated value as a variable and simply add values to it instead of recalculating on each input. For example
$('document').ready(function(){
var total = 0;
var input1 = $('#input1');
var input2 = $('#input1');
var input3 = $('#input1');
var output = $('#output');
input1.keyup(function(e){
var value = Number(this.value);
sum += value;
output.val(sum);
});
});

BigCommerce Stencil - Product Variant Stock Levels

A client wants to set up A/B testing on the Product Detail Page related to the stock_level of a product's variants. Once the user selects their options, if the quantity is less than 5, I'd show something like "Hurry, only 3 more in stock"...
I believe I have the correct Inventory settings enabled, because I can retrieve the stock_level of a product without options.
Has anyone had success pulling variant SKU stock_levels in stencil?
Thanks
This can be done using javascript in the assets/js/theme/common/product-details.js file. On initial page load and each time a product option is changed, there is a function updateView(data) that is called. The data parameter contains all the info you need for the selected variation.
Starting on line 285, replace this:
updateView(data) {
const viewModel = this.getViewModel(this.$scope);
this.showMessageBox(data.stock_message || data.purchasing_message);
with this:
updateView(data) {
const viewModel = this.getViewModel(this.$scope);
if(data.stock < "5") {
data.stock_message = "Hurry, only " + data.stock + " left!";
}
this.showMessageBox(data.stock_message || data.purchasing_message);

Creating a user generated list in flash

I'm trying to create a flash application that will keep track of user generated values. The app should basically allow the user to input the name of the item and it's cost. The total costs should then be added up to show a total value to the user. I can probably figure out how to add the values together, but I'm not really sure how to allow the user to create a list and then allow the user to save it. Can anyone point me towards a tutorial or point me in the right direction?
I am using variables to add user inputed numbers to come up with a total. The first problem is that actionscript 3.0 does not allow variables for texts. I just converted it to 2.0 to fix this. The second problem, is when I test the app and put in my values and click submit, I get NaN in the total values field. Is there a reason why it wouldn't add the values?
Here is the code I used for the submit button:
on (release) {
total = Number(rent) + Number(food) + Number(travel) + Number(entertainment) + Number(bills);
}
Am I missing anything?
Can I give the input text instance names and then give them variables? How are some ways to go about this?
Thanks for the help!
Have an object array, say for example
var stack:Array = new Array();
Then push the item name and it's cost to that array when user inputs, like
stack.push({item:AAA, cost:xx});
So that you can generate the list whenever you want with that array.
You have to see how this works in code. A list in actionscript could be stored inside an array, vector, dictionary or even an Object.
Var myList:Array = [];
myList.push({name: "item 1", cost: 5 });
myList.push({name: "item 2", cost: 7.5 });
If you want to grab the 'product' of "item 1" from the list, you have to create a function for that, lets call it getProductByName
function getProductByName(name:String):Object
{
for each(var product:Object in myList)
{
if (product.name === name) return product;
}
return null; // no match found
}
You can call that function like this:
var product = getProductByName("item 1");
trace(product.cost); // 5
And you can alter the product, so lets make it more expensive
product.cost += 1;
trace(product.cost); // 6
Have fun! If you are using classes, you would create one for the product, with public name and cost, and in that case you'de better use a vector, to ensure working with the right type.
This is what fixed the issue for me in action script 3.0:
myButton.addEventListener(MouseEvent.CLICK, addThem);
function addThem(e:MouseEvent)
{
totalField.text = String ( Number(field1.text) + Number(field2.text) + ....);
}
I also had to name the instances appropriately.