I am in the process of writing a custom module whereby the user can enter a shipping cost per product.
I have added a custom variable to all of my products called 'initial_shipping_charge'. In my Get Shipping Rate function I am trying to loop through the products in my basket and get the variable (these will be added to a overall total).
The var_dump($shipping_price_initial); line returns NULL and not the variable that it contains - any idea why this is not working?
Thanks in advance.
protected function _getStandardShippingRate()
{
$rate = Mage::getModel('shipping/rate_result_method');
/* #var $rate Mage_Shipping_Model_Rate_Result_Method */
$rate->setCarrier($this->_code);
/**
* getConfigData(config_key) returns the configuration value for the
* carriers/[carrier_code]/[config_key]
*/
$shipping_price_value = rand(10 , 50);
//Create a basket session object
$session = Mage::getSingleton('checkout/session');
foreach ($session->getQuote()->getAllItems() as $item) {
$item_id = $item->getId();
$_basketProduct = Mage::getModel('catalog/product')->load($item_id);
$shipping_price_initial = $_basketProduct->getAttribute('initial_shipping_charge');
var_dump($shipping_price_initial);
}
$rate->setCarrierTitle($this->getConfigData('title'));
$rate->setMethod('standand');
$rate->setMethodTitle('Standard');
$rate->setPrice($shipping_price_value);
$rate->setCost(0);
return $rate;
}
$_basketProduct->getAttribute('initial_shipping_charge');
Should be
$_basketProduct->getData('initial_shipping_charge');
or
$_basketProduct->getInitialShippingCharge();
But i guess you don't even have the product cause you fetch the quote item id and try to load a product with that id....you need to:
$item->getProduct()
P.S.:
Product->Quote->Order
There is a conversion process, look for "quote to order conversion" or "product to quote item conversion"
Related
BACKGROUND
I have two tables in my database, CampaignList and CampaignProduct. The logic is simple, if the user successfuly purchases products I have to create one new CampaignList and CampaignProducts based for the number of products bought. These two tables will be mapped together in the future, right now I am just trying to insert them correctly.
So for example if a user successfuly buys 3 products one new table is inserted in CampaignList and 3 new tables is CampaignProduct.
Now the products are storred in session like this:
11 => 2
29 => 1
The key is the id of the product and the value is the quantity. So this session has 3 products, two products with the id of 11 and one products with the id of 29. Now for the problem and the code.
THE PROBLEM
The inserts are working correctly, except one. I need to save the quantity of the product too in the database. But this way that I am creating I dont think i can? Because I am creating tables in a different loop where the quantity is never iterated? Here is the code
THE CODE
if ($session->has('cart') && count($session->get('cart')) > 0) {
// if the session is good create the new campaign
$campaign = New CampaignList();
$campaign->setUserId($user->getId());
$campaign->setName('Karpedeal');
$campaign->setState(1);
$em->persist($campaign);
$em->flush();
foreach ($cart as $id => $quantity) {
// store the product ids in an array
$productIds[] = $id;
//get all the products based on the id array
$product = $em->getRepository('MpShopBundle:Product')->findById($productIds);
}
// for each new product create new CampaignProduct
foreach($product as $item){
$campaignProduct = New CampaignProduct();
$campaignProduct->setCampaignId($campaign->getId());
$campaignProduct->setProductId($item->getId());
$campaignProduct->setSellingPrice($item->getPrice());
$campaignProduct->setStock($item->getStockAvailable());
$campaignProduct->setReserved($quantity); // PROBLEM how to get the quantity from the session??
$em->persist($campaignProduct);
$em->flush();
}
THE OTHER WAY
The only way I think I can do is do everything in the first foreach loop, however that way I am getting an error when I try to get the ids of the products, because they are not objects, but arrays...
if ($session->has('cart') && count($session->get('cart')) > 0) {
// if the session is good create the new campaign
$campaign = New CampaignList();
$campaign->setUserId($user->getId());
$campaign->setName('Karpedeal');
$campaign->setState(1);
$em->persist($campaign);
$em->flush();
foreach ($cart as $id => $quantity) {
// store the product ids in an array
$productIds[] = $id;
//get all the products based on the id array
$product = $em->getRepository('MpShopBundle:Product')->findById($productIds);
$campaignProduct = New CampaignProduct();
$campaignProduct->setCampaignId($campaign->getId());
$campaignProduct->setProductId($product->getId()); // the error here because $product is an array not object
$campaignProduct->setSellingPrice($item->getPrice());
$campaignProduct->setStock($product->getStockAvailable());
$campaignProduct->setReserved($quantity); // PROBLEM how to get the quantity from the session??
$em->persist($campaignProduct);
$em->flush();
}
Any ideas?
By looking at your code I guess your second answer is best, but instead of getting all products with an array of product ids, just get one product every run which results in your product to be an instance of MpShopBundle:Product.
tl;dr
changing this
$product = $em->getRepository('MpShopBundle:Product')->findById($productIds);
into this
$product = $em->getRepository('MpShopBundle:Product')->find($id);
Should work
I would like to change the value from custom features (Product Features) (Artikeleigenschaften) in my own class.
To change product values is very simple:
$productObj = new Product($produktId);
$productObj->setFieldsToUpdate(array('ean13'));
$productObj->ean13 = "johndoo";
$productObj->save();
But is there a similar way to change the Product Features?
Here my solution:
To Update Product Features you have to update 2 tables in your Database:
ps_feature_value and ps_feature_value_lang
Do this with:
$id_feature_value = (int)FeatureValue::addFeatureValueImport
(
(int)$feature['id_feature'],
$newFeatureValue,
(int)$produktId,
$this->context->language->id,
1 // $custom
);
Product::addFeatureProductImport(
$produktId, (int)$feature['id_feature'], $id_feature_value
);
To Update Features with NOT custom Values use:
$arr = FeatureValue::getFeatureValueLang($id_dropdown);
foreach ( $arr AS $item)
if ($item['id_lang'] == $this->context->language->id)
{
$feature_val_name = $item['value'] ;
}
$id_feature_value = (int)FeatureValue::addFeatureValueImport
(
(int)$feature['id_feature'],
$feature_val_name,
$product->id,
$this->context->language->id,
false
);
$product->addFeatureProductImport( $product->id , (int)$feature['id_feature'], $id_feature_value );
Mh, Ive never tried to do this, but you could try this:
Product::addFeatureProductImport($productObjc->id, $id_feature, $id_feature_value);
I created this function based upon ImportControlloer. Add this function to Product.php override and use it
/**
* Adds a feature value to a Product. If You want to have feature selected from dropdown leave $custom to false.
* #param int|$id_product
* #param int|$id_feature_group
* #param string|$feature_value
* #param bool|true $custom
* #param int|$id_lang
* #return bool
*/
public static function setFeatureValueToProduct($id_product, $id_feature_group, $feature_value, $custom = false, $id_lang)
{
$group_feature_obj = Feature::getFeature($id_lang, $id_feature_group);
if ($group_feature_obj){
$feature_name = $group_feature_obj['name'];
$feature_value = $feature_value ? trim($feature_value) : '';
$position = false;
if (!empty($feature_name) && !empty($feature_value)) {
$id_feature = (int)FeatureCore::addFeatureImport($feature_name, $position);
$id_feature_value = (int)FeatureValue::addFeatureValueImport($id_feature, $feature_value, $id_product, $id_lang, $custom);
if (Product::addFeatureProductImport($id_product, $id_feature, $id_feature_value)){
Feature::cleanPositions();
return true;
} else {
return;
}
}
} else {
return;
}
}
Example of use:
Product::setFeatureValue(1597, 15, 'Dracarys', false, 1);
It will upload Dropdown value "Dracarys" into product with ID 1597 and group feature with ID 15. If You want to make it custom value (not-predefined) just set 4th parametr to true.
Product::addFeatureProductImport($productObjc->id, $id_feature, $id_feature_value);
This will assign the feature and its value to the product. But we need the id of the feature and values. The below code is working for me.
$feature = new Feature();
$feature->name = [
$lang_id => "string"
];
$feature->position = Feature::getHigherPosition() + 1;
$feature->add();
$featurevalue = new FeatureValue;
$featurevalue->id_feature = $feature->id;
$featurevalue->value = [
$lang_id => "string"
];
$featurevalue->add();
$product->addFeatureProductImport($product->id, $feature->id, $featurevalue->id);
None of these suggestions worked for me. I had few requirements for this:
import should be able to run multiple times without duplicating a feature in product or feature values list
custom values should not be deleted even if they have the same "size" feature
no need to handle languages or multistore (thanks to datasource for only supporting one language and one shop)
This worked for me
/**
* Add or customize feature value to product feature and assign it to product
*
* Creates the feature and the value if they dont exists already
*/
private function _upsertFeatureValueToProduct($product, $featureName, $featureValue, $custom = false)
{
$id_lang = null;
// this creates the feature if required and always returns the id_feature
$id_feature = (int) \Feature::addFeatureImport($featureName);
if ($custom) {
// create or find a custom value
$id_feature_value = (int) \FeatureValue::addFeatureValueImport($id_feature, $featureValue, $product->id, $id_lang, $custom);
// change the existing value to new
$feature_value = new \FeatureValue($id_feature_value);
// in all languages (source only gives one language data)
$feature_value->value = array_fill_keys(\Language::getIDs(false), $featureValue);
$feature_value->update();
// assign the feature to the product (not strictly required if this was a update but doesn't hurt)
Product::addFeatureProductImport($product->id, $id_feature, $id_feature_value);
} else {
// non custom values. we first need to add the value and then assign it
// prestashop uses lang to find the existing feature. use the default language
$id_lang = \Configuration::get('PS_LANG_DEFAULT');
// add or get the feature id for this value
$id_feature_value = (int) \FeatureValue::addFeatureValueImport($id_feature, $featureValue, null, $id_lang, $custom);
// NOTE: product can have the same feature multiple times. But in this case we only get one value from source and want to update the feature
// so before adding the featurevalue delete existing feature assignments
$this->_deleteProductFeatureValueAssignments($product->id, $id_feature);
// assign the feature to the product
Product::addFeatureProductImport($product->id, $id_feature, $id_feature_value);
}
// required? doesn't hurt :)
\Feature::cleanPositions();
}
/**
* Delete product feature value assingments
*
* There is no api for this.. idea stolen from
*
https://github.com/PrestaShop/PrestaShop/blob/2c059b93062ce25bbde22355982e651215544e81/classes/Product.php#L2673
*
* TODO: open feature request, and remove this when prestashop has an api for this.
*/
private function _deleteProductFeatureValueAssignments($id_product, $id_feature)
{
$sql = '
DELETE fp
FROM `' . _DB_PREFIX_ . 'feature_product` fp
LEFT JOIN `' . _DB_PREFIX_ . 'feature_value` fv
ON fv.`id_feature_value` = fp.`id_feature_value`
WHERE
fp.`id_product` = ' . (int) $id_product .
' AND fp.`id_feature` = ' . (int) $id_feature .
' AND `custom`=0
';
return \Db::getInstance()->execute($sql);
}
now I can call this all day long without getting duplicate values in features or in the products
$this->_upsertFeatureValueToProduct($product, 'size', 'L');
$this->_upsertFeatureValueToProduct($product, 'size', 'XL');
$this->_upsertFeatureValueToProduct($product, 'size', 'custom', true); // one custom size just for fun
$this->_upsertFeatureValueToProduct($product, 'size', 'L');
Leaves me with two ready made feature values L and XL and a product with size=L and size=custom
screenshot of product features
There might still be bugs, just got this working :D
I have a problem updating a multidimensional array in PHP. I'm trying to implement am e-commerce website for a project and I am having problems with the shopping cart implementation.
Basically, I use a session to track the items the user adds to the shopping cart. Here's my logic in plain Pseudo code that is executed once the user clicks the add button after specifying a quantity to add for a product:
RETRIEVE 'cartItems' 2D array from SESSION array
IF 'cartItems' array does not exist in the session create new empty array and add the cartItem sub array to it with the qty and productID
ELSE Loop through the array retrieved from the SESSION array, find the product ID that matches the given product ID (index 0) and update the qty for that subarray (index 1).
Here is my PHP script addToCart.php which in turn calls another function in another script file that is included in it:
<?php
require_once("cart_utility.php");
session_start();
// Script for adding a given product to the client's cart through the use of Ajax and sessions
// retrieve values from ajax request
$productID = $_GET["productID"];
$qty = $_GET["qty"];
$cartItems = null;
// use sessions to add the items to the user's cart
// retrieve the multi-dimensional cart array if it exists, otherwise create one and add it
if(isset($_SESSION["cartItems"])){
$cartItems = $_SESSION["cartItems"];
}
else{
$cartItems = array();
}
addQtyForProduct($productID, $qty, $cartItems);
$_SESSION["cartItems"] = $cartItems;
print "Session cartItems after function return: ";
var_dump($_SESSION["cartItems"]);
// return info string with new qty of cart items
print ("success-" . getTotalCartItems($cartItems));
?>
And here's the other script file that does the handling of inserting and updating the array:
<?php
// Utility functions for retrieving items from the 2D cart items array
/* The array structure is given as (example values):
* | productID | qty |
* 0 | 1 | 3 |
* 1 | 2 | 1 |
* 2 | 5 | 8 |
* 3 | 8 | 3 |
*/
// increments the qty for the given product. If it does not exist then it is added into the main session array
// $cartItems: the main 2D array with the structure given above, pass by reference to change the array
function addQtyForProduct($productID, $qty, &$cartItems)
{
foreach($cartItems as $cartItem)
{
var_dump($cartItem);
if($cartItem[0] == $productID){
//print "Quantity given to increment: $qty";
//var_dump($cartItem);
print "Qty in current session array: $cartItem[1]";
$cartItem[1] += $qty;
print "New qty in cartItem array: $cartItem[1]";
return;
}
}
// not found, therefore add it to the main items array
array_push($cartItems, array($productID, $qty));
}
// returns the total number of items in the cart
function getTotalCartItems($cartItems)
{
$total = 0;
foreach($cartItems as $cartItem)
$total += $cartItem[1];
return $total;
}
?>
I have placed some var_dump statements and can confirm that upon returning from the function 'addQtyForProduct', the array is not updated. But why? I pass the array by reference to directly alter it's contents.
It successfully adds on the first time when there is no existing array but fails to increment if the array exists.
Also, the values are successfully incremented in the 'addQtyForProduct' function but the array somehow is not updated when it returns from the function.
I would gladly appreciate some help on this. I've been trying to understand this for days now and It's driving me nuts.
As read on this page you should use references. Add a & in front of your $cartItem and your script should work. Right now PHP stores a 'copy' of every value in your array in $cartItem, rather than it's reference. So currently you are editing a copy of the original, rather than the original array.
I need to gather all of the available attributes for the given product and then create a multidimensional array with them. Hopefully you can create a multidimensional array with more than two dimensions? The resulting array declarations should look like this:
$simpleArray[$child->getVendor()][$child->getColor()]=$child->getPrice();
First I'm gathering all the attributes then adding them to a string where I can call each one later:
$_product = $this->getProduct();
$_attributes = Mage::helper('core')->decorateArray($this->getAllowAttributes());
//Gather all attribute labels for given product
foreach($_attributes as $_attribute){
$attributeString .= '[$child -> get' . ucfirst($_attribute->getLabel()) . '()]';
}
Then I'm attempting to append that string to the array to declare it:
foreach($childProducts as $child) { //cycle through simple products to find applicable
//CAITLIN you are going to need way to search for other attributes, GET list of attributes
$simpleArray. $attributeString =$child->getPrice();
}
Mage::log('The attributeString is '. $simpleArray. $attributeString, null, 'caitlin.log'); //This is logging as "The attributeString is Array74"
Any suggestions?
You'll need to use recursion to do what you're requesting without knowing the attribute names while writing the code.
This will loop through and provide all of the child product prices, in a multi dimensional array based on the configurable attributes. It assumes that $_product is the current product.
$attrs = $_product->getTypeInstance(true)->getConfigurableAttributesAsArray($_product);
$map = array();
foreach($attrs as $attr) {
$map[] = $attr['attribute_code'];
}
$childPricing = array();
$childProducts = $_product->getTypeInstance()->getUsedProducts();
foreach($childProducts as $_child) {
// not all of the child's attributes are accessible, unless we properly load the full product
$_child = Mage::getModel('catalog/product')->load($_child->getId());
$topLevel = array($child->getData($map[sizeof($map)]) => $_child->getPrice());
array_pop($map);
$childProducts = array_merge($childProducts,$this->workThroughAttrMap($map,$_child,$topLevel));
}
//print_r childProducts to test, later do whatever you were originally planning with it.
In the same controller include this:
protected function workThroughAttrMap(&$map,$child,$topLevel) {
$topLevel = array($child->getData($map[sizeof($map)]) => $topLevel);
array_pop($map);
if(sizeof($map) > 0) return workThroughAttrMap($map,$child,$topLevel);
else return $topLevel;
}
I haven't tested this code so there may be a few minor bugs.
There are a few things you could do to make the code a bit cleaner, such as moving the first $topLevel code into the function, making that an optional parameter and initializing it with the price when it doesn't exist. I also haven't included any error checking (if the product isn't configurable, the child product doesn't have its price set, etc).
In our order proces it is possible to send an invoice for a partial order. So when a couple of order lines are being shipped, an invoice have to be send also.
To make this possible I use this code:
$invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice($items);
if (!$invoice->getTotalQty()) {
Mage::throwException(Mage::helper('core')->__('Cannot create an invoice without products.'));
}
$invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE);
$invoice->register();
$transactionSave = Mage::getModel('core/resource_transaction')
->addObject($invoice)
->addObject($invoice->getOrder());
$transactionSave->save();
$invoice->sendEmail();
$invoice->setEmailSent(true);
$invoice->save();
Where the $items variable is an array containing the order ids and the amount of products to be invoiced.
The created invoice shows the correct products to be invoiced, but somehow the totals aren't updated. The totals still are the totals of the complete order, instead of the partial invoice.
I probably have to update or recalculate the totals but can't find the right code to force the update.
Anyone around who can put me in the right direction?
Well, it seems I have found the problem. The functionality as described above works manually executing it in the administrator interface. The code as enclosed above I only got to work by changing a core file of Magento.
If you change line 103 of Mage_Sales_Model_Service_Order from continue; to $qty = 0; the functionality works.
In short, this is what happens. With continue the second row item isn't added to the invoice which the invoice makes thinks the curren item is the last item of the whole order and therefore needs to invoice the complete outstanding amount. In my case the invoice I did want to invoice and the row I didn't want to invoice.
I've submitted it as issue on the Magento issue list.
Today I faced with exactly this problem, but I found a more elegant way to solve it without editing the core. The solution is to pass the products that we don't want to invoice, with 0 quantity.
In this way, the code you changed in core will act exactly like in your solution :)
As an example if I have 2 products in my order:
array(
1234 => 1,
1235 => 2
)
passing this array:
$qtys = array(
1234 => 1,
1235 => 0
)
will force this code:
// Mage_Sales_Model_Service_Order: lines 97-103
if (isset($qtys[$orderItem->getId()])) { // here's the magic
$qty = (float) $qtys[$orderItem->getId()];
} elseif (!count($qtys)) {
$qty = $orderItem->getQtyToInvoice();
} else {
continue; // the line to edit according to previous solution
}
to act exactly like in your solution, so you don't have to edit core code.
Hope it helps :)
OK - took me a bit, but now I see how to correctly create the array.
foreach ($items as $itemId => $item) {
$itemQtyToShip = $item->getQtyToShip()*1;
if ($itemQtyToShip>0) {
$itemQtyOnHand = $stockItem->getQty()*1;
if ($itemQtyOnHand>0) {
//use the order item id as key
//set the amount to invoice for as the value
$toShip[$item->getId()] = $itemQtyToShip;
} else {
//if not shipping the item set the qty to 0
$toShip[$item->getId()] = 0;
}
}
$invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice($toShip);
This creates a proper invoice.