I need to add some JavaScript to the order confirmation page that includes details about the order. Although I can access the order id through a BigCommerce global variable, I cannot work out how to get the rest of the order details into my JavaScript.
For instance, I can access the BigCommerce order_id global %%GLOBAL_OrderId%% and use that in a JavaScript alert, but I also need to access the following:
order total
order tax
order shipping
order postcode
And foreach product in the order
product_id
unit_price
quantity
There these global items but when I try to access them they are blank, I presume that I need to loop through the cart contents.
%%GLOBAL_ProductModel%%
%%GLOBAL_ProductPrice%%
%%GLOBAL_ProductQty%%
I have read all the docs I can find. Can anyone give me an idea of how to achieve this. I need the values so I can pass them to a third party JS function for their use. All of that is waiting and ready but I cannot get the data out of Big Commerce templating system. The data is there, on the order.html template page, as the social sharing panel reads it, but again I cannot see how the social sharing snippet is accessing it.
I created a hacky script just for you that pulls the product data (as well as some order details).
It parses the data from the %%GLOBAL_ConversionCode%% template variable, and as such this script should be inserted in order.html immediately after the %%GLOBAL_ConversionCode%% variable.
Specifically, %%GLOBAL_ConversionCode%% outputs to:
<!-- Include the conversion tracking code for all analytics packages -->
<!-- Start conversion code for analytics_googleanalytics -->
<script type="text/javascript">
if(typeof(pageTracker) != 'undefined') {
pageTracker._addTrans(
'196',
'store-name',
'0.00',
'2.12',
'1.92',
'Austin',
'Texas',
'United States'
);
pageTracker._addItem(
'196',
'2004',
'TAKE YOUR TIME: Sweet Body Butter',
'',
'24.96',
'1'
);
pageTracker._trackTrans();
}
</script>
Solution:
<script>
//-------------- Main --------------//
//** Create the order data array from analytics script **//
var data = parseAnalyticsData(getAnalyticsScript());
//console.log(data);
/**
* Retrieve the order details as an object, properties are:
* id - The order ID.
* shipping - The order shipping cost.
* tax - The order tax cost.
* shippingTax - The order shipping tax cost.
* city - The order shipping city.
* state - The order shipping state.
* country - The order shipping country.
*/
var orderDetails = getOrderDetails(data);
console.log("Order ID = %d", orderDetails.id);
console.log("Order shipping city = %s", orderDetails.city);
console.log("Order subtotal = %f", orderDetails.subtotal);
/**
* Retrieve the order product details, as an array of product objects.
* Properties are:
* id - The product ID.
* description - The product description.
* tax - The product tax cost.
* price - The product price per product.
* qty - The product quantity purchased.
*/
var products = getOrderProducts(data);
//** Loop through the products array to access each product **//
console.log("Total number of products = %d", products.length);
for (x=0; x<products.length; x++) {
console.log("--------");
console.log("Item # ", x+1);
console.log("Product ID = %f", products[x].id);
console.log("Product QTY = %f", products[x].qty);
console.log("Product Price = %f", products[x].price);
console.log("--------");
}
//-------------- Functions --------------//
/**
* Parses the DOM to retrieve the order data analytics script.
*/
function getAnalyticsScript() {
var scripts = document.getElementsByTagName('script');
var thisScriptTag = scripts[scripts.length - 2];
var data = thisScriptTag.textContent || thisScriptTag.innerText;
return data;
}
/**
* Parses the raw analytics script element to remove all script
* text, and parse just the order related data into an array.
* #param script <String> - The raw order analytics script.
* #return <mixed> - Array containing the order data.
*/
function parseAnalyticsData(data) {
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
// This is hacky, and probably inefficient, but it removes all
// script related text, so the end result is just a comma separated
// array of the order and product data.
data = data.replace("if(typeof(pageTracker) != 'undefined') {", '');
data = data.replaceAll( 'pageTracker._addTrans(', '');
data = data.replaceAll( ' pageTracker._trackTrans();', '');
data = data.replaceAll( 'pageTracker._addItem(', '');
data = data.replaceAll(');', '');
data = data.replace('}', '');
data = data.replace( /\n/g, ",").replaceAll( ",,",",");
data = data.replace(/\s/g,'');
data = data.split(',');
data = cleanArray(data); // Remove all empty values from array.
return data;
}
/**
* Removes all empty data from array.
* #param array <mixed> - The array to clean.
*/
function cleanArray(array) {
var newArray = new Array();
for (var i = 0; i < array.length; i++) {
if (array[i]) {
newArray.push(array[i]);
}
}
return newArray;
}
/**
* Parse Analytics Data for Order Details
* #param data <mixed> - The order analytics data.
* #return <mixed> - Object containing the order details.
*/
function getOrderDetails(data) {
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
return {
id : parseFloat(data[0].replaceAll("'",'')),
subtotal : ( parseFloat(data[2].replaceAll("'",'')) - (parseFloat(data[3].replaceAll("'",'')) + parseFloat(data[4].replaceAll("'",'')) ) ),
total : parseFloat(data[2].replaceAll("'",'')),
tax : parseFloat(data[3].replaceAll("'",'')),
shipping : parseFloat(data[4].replaceAll("'",'')),
city : data[5].replaceAll("'",''),
state : data[6].replaceAll("'",''),
country : data[7].replaceAll("'",'')
}
}
/**
* Parse Analytics Data for All Order Product Details.
* #param data <mixed> - The order analytics data.
* #return <mixed> - Array containing individual product details.
*/
function getOrderProducts(data) {
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.split(search).join(replacement);
};
var counter = -1; // Keep index of details per product.
var productsArray = []; // Init empty array to hold all products.
var product = {}; // Init empty object to hold single product data.
//** Product data starts at index 8 **//
for (x=8; x<data.length; x++) {
counter++;
switch (counter) {
case 1:
product.id = parseFloat(data[x].replaceAll("'",''));
break;
case 2:
product.description = data[x].replaceAll("'",'');
break;
case 3:
product.tax = parseFloat(data[x].replaceAll("'",''));
break;
case 4:
product.price = parseFloat(data[x].replaceAll("'",''));
break;
case 5:
product.qty = parseFloat(data[x].replaceAll("'",''));
counter = -1; // reset counter
productsArray.push(product); // push product to products array
product = {};
break;
}
}
return productsArray;
}
</script>
Related
I have a before load user event function on an invoice record that create a button called 'create vendor bill'.
When this button is pressed, a new vendor bill record is opened. The UE script:
/**
*#NApiVersion 2.x
*#NScriptType UserEventScript
*/
define([
"N/url",
"N/record",
"N/runtime",
"N/ui/serverWidget",
"N/redirect",
], function (url, record, runtime, serverWidget, redirect) {
var exports = {};
/**
* #param {UserEventContext.beforeLoad} context
*/
function beforeLoad(context) {
if (
context.type == context.UserEventType.EDIT ||
context.type == context.UserEventType.VIEW
) {
var record = context.newRecord;
var recordId = record.id;
var recordType = record.type;
var customer = record.getValue({ fieldId: "entity" });
log.debug("entity", customer);
var scriptObj = runtime.getCurrentScript();
var customForm = scriptObj.getParameter({
name: "custscript_custom_form_vb",
});
var recordSublist = record.getSublist({ sublistId: "item" });
log.debug("item", recordSublist);
var form = context.form;
log.debug("form", form);
var userVarStr = record;
log.debug("uservarstr", userVarStr);
var userVarURL = url.resolveRecord({
recordType: "vendorbill",
params: {
entity: parseInt(customer),
supportcase: recordId,
cf: parseInt(customForm),
},
});
form.addButton({
id: "custpage_button_test",
label: "Create Vendor Bill",
functionName: "getDetails('" + userVarURL + "')",
});
}
}
exports.beforeLoad = beforeLoad;
return exports;
});
Once the page redirects to the vendor bill form, a client script (deployed on the form), sets the field values on the body of the vendor bill using the parameters passed in the url
This is working as expected.
Where I am getting stuck is trying to work out how to pass the 'item' sublist values to from the invoice to the vendor bill?
Would I pass this as an array?
From what I understand, there is a limit to the number of characters that can be passed via the url.
I can't find anything online or in the Netsuite documentation that deals with passing sublist values between records
For starters I would want to see the Client Script.
One option would be to only pass the Invoice Record ID and Type. Then you can create a Suitelet to be used as a proxy and get the sublist data by a saved search.
Something to keep in mind is that if the sublist is very very long you may reach a execution timeout so you may want to consider triggering a MapReduce script to populate the sublist again you would pass it the recType and ID of the invoice and vendor bill and then use a saved search to get the data.
There are other approaches but I would need to see the client script.
I manually manage my stocks on Prestashop. I am looking for a solution to automatically return the initial stock after a sales order.
For example, a product is ordered in two copies with an initial stock of 7. I would like the stock to remain at 7 after the order and not at 5.
Do you know a technique that would allow me to realize this automatically?
Put a Hook on Order Confirmation (displayOrderConfirmation) in a new module (you can generate one at https://validator.prestashop.com/) and check whats inside the cart then put it again in your stocks :
public function hookDisplayOrderConfirmation($params) {
$order = $params['order'];
$cart = new Cart($order->id_cart);
$products = $cart->getProducts();
foreach ($products as $product) {
$removed_qty = (int) $product['quantity'];
$past_qty = (int) StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute']);
$new_qty = $removed_qty + $past_qty;
StockAvailable::setQuantity($product['id_product'], $product['id_product_attribute'], $new_qty);
}
}
Has anyone tried to create a plugin that updates the record's values in the Quote Product form? I created one, because I need a custom formula that calculates the Extended Amount field, but there are automatic calculations in the CRM that fill these fields. This doesn't allow me to update the formula of calculation at all.
What my plugin do, is:
Gets the values from the fields Price per unit, Quantity and Discount % (which is a custom field);
Calculates the value that I need;
Sets it at the extended amount field.
But, none of this works because I get a "Business Process Error";
Basically this error tells me that I can't use the record's guid to access it.
Here is my code:
protected void ExecutePostQuoteProductUpdate(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
Guid quoteProductID = (Guid)((Entity)context.InputParameters["Target"]).Id;
ColumnSet set = new ColumnSet();
set.AllColumns = true;
var quote = service.Retrieve("quotedetail", quoteProductID, set);
var priceperunit = quote.Attributes["priceperunit"];
var teamleader = quote.Attributes["new_disc"];
var manualdiscountamount = quote.Attributes["manualdiscountamount"];
var volumediscountamount = quote.Attributes["volumediscountamount"];
var VAT = (int)quote.Attributes["new_vat"];
var discountamount = (double)priceperunit * (double)teamleader / 100;
var baseamount = (double)priceperunit - discountamount;
var tax = baseamount * VAT / 100;
var extendedamount = baseamount + tax;
quote.Attributes["new_discountamount"] = discountamount;
quote.Attributes["baseamount"] = baseamount;
quote.Attributes["tax"] = tax;
quote["description"] = priceperunit;
quote.Attributes["extendedamount"] = extendedamount;
service.Update(quote);
}
Please, tell me if there is a way to access those fields and use/set them.
Thanks in Advance!
:/
You cannot update extendedamount directly - instead if you use the built-in fields which automatically calculate it then CRM will take care of this for you.
Those fields are: baseamount, quantity, discountamount, and tax. These are Money fields so you will need to store them as such:
quote.Attributes["baseamount"] = new Money(baseamount); // Money and decimal numbers use the "decimal" datatype and not double.
As for your custom calculation, you just need to convert the percentage stored in your custom field into the actual amount and assign it to the discountamount attribute. Something like:
var discountamount = (decimal)priceperunit * (decimal)teamleader / 100;
// Assigning the actual discount amount will automatically recalculate the extended amount on the line.
// No need to recalculate all fields.
quote.Attributes["discountamount"] = new Money(discountamount);
Note that tax may need to be recalculated to reflect the discount, but the process should work exactly the same.
I need to display (tier) prices based on the qty increments of a product. E.g. a simple product, with a regular price of 50ยข, no taxes and qty increments of 20 should be displayed on product views with "$10 per 20".
Without using taxes this should be quite easy. But there seems to be no "default" helper or model to do this with taxes enabled and different calulation algorithms (e.g. Mage_Tax_Model_Calculation::CALC_UNIT_BASE); expect for quotes in Mage_Tax_Model_Sales_Total_Quote_Tax and Mage_Tax_Model_Sales_Total_Quote_Subtotal.
Did I miss something here, or do I have to write the business logic to calculate the price on my own? And how I would best encapsulate it?
I solved my problem for now very simple. For this I used the <rewrite> element in a custom module to expand the helper Mage_Tax_Helper_Data with an additional instance method:
class My_Module_Helper_Tax_Data extends Mage_Tax_Helper_Data {
/**
* Get product price based on stock quantity increments
*
* #param Mage_Catalog_Model_Product $product
* #param float $price inputed product price
* #param null|int $qtyIncrements inputed stock quantity increments
* #param null|bool $includingTax return price include tax flag
* #param null|Mage_Customer_Model_Address $shippingAddress
* #param null|Mage_Customer_Model_Address $billingAddress
* #param null|int $customerTaxClass customer tax class
* #param mixed $store
* #param bool $priceIncludesTax flag what price parameter contain tax
* #return float
*/
public function getQtyIncrementsPrice($product, $price, $qtyIncrements = 1,
$includingTax = null, $shippingAddress = null, $billingAddress = null,
$customerTaxClass = null, $store = null, $priceIncludesTax = null) {
$store = Mage::app()->getStore($store);
$qtyIncrements *= 1;
if ($this->_config->getAlgorithm($store)
== Mage_Tax_Model_Calculation::CALC_UNIT_BASE) {
$price = $this->getPrice(
$product, $price, $includingTax, $shippingAddress,
$billingAddress, $customerTaxClass, $store, $priceIncludesTax
);
$price *= $qtyIncrements;
$price = $store->roundPrice($price);
} else {
$price *= $qtyIncrements;
$price = $this->getPrice(
$product, $price, $includingTax, $shippingAddress,
$billingAddress, $customerTaxClass, $store, $priceIncludesTax
);
}
return $price;
}
}
It can be later used in a custom rewrite of methods such as Mage_Catalog_Block_Product_Price::getTierPrices().
We've got some API integrations that will periodically create shipments for orders.
What I'd like to do is create an observer to also create an appropriate invoice & capture payment when this shipment is created. I have this tied to sales_order_shipment_save_after:
public function autoInvoice($observer){
$shipment = $observer->getEvent()->getShipment();
$order = $shipment->getOrder();
$items = $shipment->getItemsCollection();
$qty = array();
foreach($items as $item)
$qty[$item['order_item_id']] = $item['qty'];
$invoice = Mage::getModel('sales/order_invoice_api');
$invoiceId = $invoice->create($order->getIncrementId(), $qty);
$invoice->capture($invoiceId);
}
(The code for the actual capture is somewhat naive, but bear with me.)
What's strange is that this code works just fine -- the shipment is created, the invoice is created and marked as 'Paid.' However, the order itself stays in limbo and retains a status 'Pending.'
Looking into it further, the items on the order itself have the correct quantities for both Ordered and Shipped, but there's no listing of the quantity Invoiced. I think this is what's causing the status hangup. It's as though the qty_invoiced on the sales_order_item table is getting reverted somehow.
Again, the Invoice shows the right items, so I'm quite confused here.
Any ideas?
This is indeed a very interesting one #bahoo.
maybe try:
$shipment = $observer->getEvent()->getShipment();
$order = $shipment->getOrder();
$qty = array();
$invoice = Mage::getModel('sales/order_invoice_api');
$invoiceId = $invoice->create($order->getIncrementId(), $qty);
$invoice->capture($invoiceId);
After lots of testing using the API, I found if I created the invoice first, then the shipment, Magento Enterprise 1.13.01. would correctly set the order status to Complete. Trying to make the shipment first, then the invoice, would result in the Pending Order status remaining even if all items had been invoiced and shipped.
The bridging system code below uses information about orders placed in Magento, routed to the bridging system via an Observer on checkout_submit_all_after, sent to NetSuite via web services, and fulfilled in NetSuite. The bridging system gets sales order fulfillments from NetSuite via web service, and saves items shipped, package, and tracking information, to use in the code below.
Code shows creating invoice, then shipment and tracking.
Note that while testing, I just saw Magento incorrectly create an invoice for all three items in an order, even though it was passed an array that just contained two of the items. Magento did correctly create a shipment record for just the two items. Puzzling that the invoice API and the shipment API when passed the same array of items and quantities have such different behavior. Anyone else seen this?
$proxy = new SoapClient($proxyUrl); /* V2 version */
$sessionId = $proxy->login($apiUser, $apiKey);
try
{ /* try to create invoice in Magento */
$invoiceIncrementId = $proxy->salesOrderInvoiceCreate($sessionId, $orderIncrementId, $shipItemsQty, 'invoice created', false, false);
}
catch( SoapFault $fault )
{
$error = $fault->getMessage(); /* will return 'Cannot do invoice for order' if invoice already exists for these items */
}
if (!stristr($error,'Cannot do invoice') and !empty($error))
{ /* some other invoicing problem, log what returned, on to next order */
$ins = "insert into order_error_log values(NULL, ".$orderId.", '".date("Y-m-d H:i:s")."', '".$program."', '".$error."')";
$result = $mysqli->query($ins);
$upd = "update orders set orderStatusId = ".ERROR_ADDING_MAGENTO_INVOICE.",
dateStatusUpdated = '".date("Y-m-d H:i:s")."'
where id = ".$orderId;
$result = $mysqli->query($upd);
continue;
}
if ((stristr($error,'Cannot do invoice') or empty($error)) and $complete)
{ /* if all fulfilled, may change status */
$upd = "update orders set orderStatusId = ".STORE_INVOICE_CREATED.",
dateStatusUpdated = '".date("Y-m-d H:i:s")."'
where id = ".$orderId;
$result = $mysqli->query($upd);
}
/* send Magento salesOrderShipmentCreate and get returned shipment Id, re-using proxy login and session */
$comment = 'Fulfillment(s) shipped on: '.$netsuiteShipDate;
try
{ /* returns value such as string(9) "100002515" */
$shipmentIncrementId = $proxy->salesOrderShipmentCreate($sessionId, $orderIncrementId, $shipItemsQty, $comment);
}
catch( SoapFault $fault )
{
$error = $fault->getMessage().': SOAP error received when trying to add shipment to Magento for morocco order id '.$orderId.
' store order id '.$orderIncrementId.' with items qty array of: '.var_dump($itemsQty);
$ins = "insert into order_error_log values(NULL, ".$orderId.", '".date("Y-m-d H:i:s")."', '".$program."', '".$error."')";
$result = $mysqli->query($ins);
$upd = "update orders set orderStatusId = ".MAGENTO_SOAP_EXCEPTION.",
dateStatusUpdated = '".date("Y-m-d H:i:s")."'
where id = ".$orderId;
$result = $mysqli->query($upd);
continue; /* on to next order */
}
/* Using that shipmentId, send info re each package shipped for these fulfillments to Magento via salesOrderShipmentAddTrack. */
foreach ($packageIds as $packageId => $package)
{
try
{
$trackingNumberId = $proxy->salesOrderShipmentAddTrack($sessionId, $shipmentIncrementId,
$package['carrier'], 'tracking number', $package['trackNumber']);
}
catch( SoapFault $fault )
{
$error = $fault->getMessage().': SOAP error received when trying to add tracking number '.$package['trackNumber'].' to
Magento for morocco order id '.$orderId.' store order id '.$orderIncrementId;;
$ins = "insert into order_error_log values(NULL, ".$orderId.", '".date("Y-m-d H:i:s")."', '".$program."', '".$error."')";
$result = $mysqli->query($ins);
$upd = "update orders set orderStatusId = ".MAGENTO_SOAP_EXCEPTION.",
dateStatusUpdated = '".date("Y-m-d H:i:s")."'
where id = ".$orderId;
$result = $mysqli->query($upd);
continue;
}
}