Hotcakes access variant prices via SingleProductViewModel - e-commerce

From the SingleProductViewModel, what is the best way to access prices for the variants associated with the product? From the documentation page linked above, I see that SingleProductViewModel contains a Product object, but I'm not sure how to use that to get prices of variants. (I can't find a listing of properties for the Product object).
Here is my specific use case: I have a Hotcakes Category Viewer and I'd like each product listed to display the range of prices for all variants of that product, rather than just the price for the main product. For example, a fedora product would display price as "$10 - $30" if the product contained variants with prices of $10, $20, and $30. I happen to be using the "simple" view of the category viewer, so am expecting to implement this in _RenderSingleProductSimple.cshtml, however I'm interested in using this for other category views, too.
Thanks in advance.

From what I've seen, most people will change their viewset to say something like "Starting at [PRICE]" or "As Low As [PRICE]" when there is a variant detected.
If you'd like to show the full range of prices, this can be done too, but you should know that depending on how many products that have variants and how many variants overall in the view, this could result in a negative performance impact on the site. How much impact is seen could range from negligible to undesirable.
The documentation you mentioned includes information about the Item property of the SingleProductViewModel class. This property includes all of the variant information you'd be looking for.
So, what you could do is use the Item.HasVariants property to determine if you need to have a different label. If that returns true, you can then iterate through the Item.Variants property to get all of the prices and find the lowest and highest ones to display.

Thanks #Will Strohl, that is helpful information.
I've put together the following code which seems to be achieving the original aim. Note that I said "variants" in the question, and these are product variants in our implementation, however we are achieving price adjustments for the variants via product options, so the code below looks at Model.Item.Options rather than Model.Item.Variants. Also, regarding price, I ignored user price details that weren't relevant to our implementation, and so used Model.Item.ListPrice rather than Model.UserPrice.DisplayPrice.
<div class="hc-recprice">
#{
string priceToDisplay = "";
if (Model.Item.HasOptions()){
decimal minPrice = Decimal.MaxValue;
decimal maxPrice = Decimal.MinValue;
decimal oiPrice = 0;
Hotcakes.Commerce.Catalog.OptionList options = Model.Item.Options;
foreach (Hotcakes.Commerce.Catalog.Option o in options){
foreach (Hotcakes.Commerce.Catalog.OptionItem oi in o.Items){
oiPrice = Model.Item.ListPrice + oi.PriceAdjustment;
if (oiPrice < minPrice) {
minPrice = oiPrice;
}
if (oiPrice > maxPrice) {
maxPrice = oiPrice;
}
}
}
if(minPrice == maxPrice){
priceToDisplay = string.Format("{0:C0}", minPrice);
} else {
priceToDisplay = string.Format("{0:C0}", minPrice) + " - " + string.Format("{0:C0}", maxPrice);
}
} else {
priceToDisplay = string.Format("{0:C0}", Model.Item.ListPrice);
}
#Html.Raw(priceToDisplay)
}
</div>

Related

Global / Line Discount for Sale/Purchase/Account

[Odoo 14 Community Edition]
I need to customize Global and Line Discounts (amount & percentage) into Sale / Purchase / Account.
I have done the Sale and Purchase parts. It is just adding fields and a few logics here and there and send the data to Account (account.move) by prepare_invoice methods.
Now here's the issue I am facing -- The Account. I am having a tremendous confusion of where I should modify. After I tried to understand and tracked the flow of the standard invoicing/billing, I am at lost. There are too many functions/variables for me, who do not understand accounting, to try to get the whole idea out of it.
I did add the discount fields that I need. However, the standard calculation of price / taxes / credit / debit are messed up when I try to inherit some methods that I think I should modify. I ended up having incorrect taxes, unbalanced credit/debit, and incorrect total amount.
I tried editing here and there (by inheriting of course. I can still rollback everything I did).
The point is that I need precise suggestions and/or directions of what functions/methods I should inherit just enough to make discount possible. I have 2 Line Discount fields (amount and percent) and 2 Global Discount (also amount and percent). The calculation between them is not the point of interest here. The only problem at this point is to integrate these fields (from SO, PO, and manually created) into the calculation of everything in Invoicing/Billing.
Here are the fields:
account.move
global_discount_amount = fields.Float(string='Global Discount Amount', compute=compute_global_discount_amount, store=True)
global_discount_percent = fields.Float(string='Global Discount Percent', compute=compute_global_discount_percent, store=True)
account.move.line
discount_line_amount = fields.Float(string='Disc. Line Amount', compute=compute_discount_line_amount, store=True)
discount_line_percent = fields.Float(string='Disc. Line %', compute=compute_discount_line_percent, store=True)
Right now, I am messing with some methods such as: (a few examples)
account.move
_recompute_tax_lines
account.move.line
create
_get_fields_onchange_balance_model
_get_price_total_and_subtotal_model
_onchange_price_subtotal
Most of the modifications are written by copying the whole method from standard into my new model (inherit that standard model) and edit some codes here -- Override the standard code from my understanding.
Function computation/execution either depends on other fields value change or compute every time form/listview load.
Check in your case what is depends on the function compute_global_discount_amount and compute_global_discount_percentage
For better developing/troubleshooting, remove any #depends() fields declaration on the functions. Additionally, remove the store=True attribute temporarily. It will help you to narrow down the issue. And make sure you get the correct numbers.
Once you get it, add back fields depending.
Here is a sample example of a method (Odoo 14 CE) override which will be executed during compute amount.
#api.depends(
'line_ids.matched_debit_ids.debit_move_id.move_id.payment_id.is_matched',
'line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual',
'line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual_currency',
'line_ids.matched_credit_ids.credit_move_id.move_id.payment_id.is_matched',
'line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual',
'line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual_currency',
'line_ids.debit',
'line_ids.credit',
'line_ids.currency_id',
'line_ids.amount_currency',
'line_ids.amount_residual',
'line_ids.amount_residual_currency',
'line_ids.payment_id.state',
'line_ids.full_reconcile_id')
def _compute_amount(self):
super()._compute_amount()
for record in self:
record.compute_global_discount_amount()
record.compute_global_discount_percent()
def compute_global_discount_amount(self):
for record in self:
# Execute your logic for compute global_discount_amount
record.global_discount_amount = $$$$
have a look at apply_discount function in an inherited class of sale.order
def apply_discount(self, cr, uid, ids, discount_rate):
cur_obj = self.pool.get('res.currency')
res = {}
line_obj = self.pool.get('sale.order.line')
for order in self.browse(cr, uid, ids, context=None):
for line in order.order_line:
line_obj.write(cr, uid, [line.id], {'discount': discount_rate}, context=None)
return res
A new column was added to the new inherited subclass of sale order
'discount_rate' : fields.float('Discount rate'),
Then in the sale order view (an inherited one) placed the new field discount on the sale.order.view and fired an event on the on_change of the value passing the self value of the field to the on_change event
In this way you can apply discount sequentially to the rows of the order without altering the normal process.
Firstful, please pardon my English. Then let's get into the core, I guess that this is exactly the task that I was assigned. Luckily that I was not alone, I was guided by my so-called senior which showed me the way in order to achieve the Global Discount for Invoice and Bill.
By looking at your methods listing, you were already on the right path!
So now, let me help you further...as much as I can
As in my case, I didn't put any new field regarding the Global Discount in Account Move Line model, even though in Sale Order Line and Purchase Order Line Global Discount fields do exist.
All and all, here are the methods that need to be customized:
_onchange_invoice_line_ids
_compute_amount
_recompute_payment_terms_lines
_recompute_tax_lines
I heavily modified the 3rd and 4th methods. However, I think that I still have some bugs, don't hesitate to tell me.

Aditionnal price to product in prestashop

I am selling drinks in bottles that have a deposit price. The customer has to pay that amount, but can get it back by bringing the bottles to a supermarket. I would like to show the product price without the deposit in the product pages, but when the user checks out his cart, this amount needs to be added to the cart for each item having a deposit. Here are some extra info:
Not all products have a deposit
The deposit price depends on the product
I managed to add a "deposit" field to the backoffice in the product page:
http://oi57.tinypic.com/6p9s80.jpg
But it seems that changing the whole workflow of the cart check out would be quite a pain. Is there any easy way to achieve this task?
Thanks !
easiest way to do what you want it is using "product attributes combinations" + little code changes, step by step:
create product attribute e.g. "deposit"
add value to it, any,e.g. "yes"
in product create combination with "deposit: yes", set price impact value as you need, e.g. "$10" and set it as default combination
to show on product page price without "deposit" change in themes/themename/js/product.js in function updatePrice() find lines
// Apply combination price impact
// 0 by default, +x if price is inscreased, -x if price is decreased
basePriceWithoutTax = basePriceWithoutTax + +combination.price;
and wrap it into condition:
if( your_attribute_id != combination.attributes[0] ) {
basePriceWithoutTax = basePriceWithoutTax + +combination.price;
}
but in cart you will see full price.
UPD:
I see no good way to do it in template, without core changes, so if you need it (solution also not ideal)
file controllers/front/CategoryController.php (use override) method assignProductList() change code block to next view:
foreach ($this->cat_products as &$product) {
if ($product['id_product_attribute'] && isset($product['product_attribute_minimal_quantity']))
$product['minimal_quantity'] = $product['product_attribute_minimal_quantity'];
$combination = new Combination($product['id_product_attribute']);
$attributes = $combination->getAttributesName($this->context->language->id);
foreach ($attributes as $attribute) {
if(your_attribute_id == $attribute['id_attribute'])
$product['price_tax_exc'] -= $combination->price;
}
}
you will need to repeat it for all lists controllers that you use (and you can do not use foreach but access to array element by index like you already did), in any case solution very project specific, just quick fix, in common case be better use other ways.

Magento - How do I exclude a category from product collection?

I'm trying to create some functionality that allows me to arbitrarily show a collection of products based on criteria such as Category and Attribute values and have this working as intended, however I'd also like to be able to exclude certain Categories so that I could then include the 'Default Category' and then exclude any sub categories I don't want. There doesn't appear to be a method such as the addCategoryFilter($catid) for this.
I found a similar question that had been answered but couldn't get it to work for me.
$catId = 9;
/* I'm almost positive 'e' is the alias used for catalog_product_entity, check your
query with echo (string) $products->getSelect(); if it doesn't work */
$products->getSelect()->join(array('cats' => 'catalog_category_product'), 'cats.product_id = e.entity_id');
$products->getSelect()->where('cats.category_id', array('neq' => $catId));
Has anyone else had any experience of doing this kind of thing?
I don't think there is a native way of doing it. But you can easily make a quick module that extends
Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Collection
and then you can create your own method, and change the joinCondition to suit
Check my solution:
$_productCollection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToSelect('*')
->addUrlRewrite();
Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($_productCollection);
Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($_productCollection);
$_productCollection->load();
$_productCollection->getSelect()->join(array('cats' => 'catalog_category_product'), 'cats.product_id = e.entity_id');
$_productCollection->getSelect()->where('cats.category_id not in (41)');

Magento Bulk update attributes

I am missing the SQL out of this to Bulk update attributes by SKU/UPC.
Running EE1.10 FYI
I have all the rest of the code working but I"m not sure the who/what/why of
actually updating our attributes, and haven't been able to find them, my logic
is
Open a CSV and grab all skus and associated attrib into a 2d array
Parse the SKU into an entity_id
Take the entity_id and the attribute and run updates until finished
Take the rest of the day of since its Friday
Here's my (almost finished) code, I would GREATLY appreciate some help.
/**
* FUNCTION: updateAttrib
*
* REQS: $db_magento
* Session resource
*
* REQS: entity_id
* Product entity value
*
* REQS: $attrib
* Attribute to alter
*
*/
See my response for working production code. Hope this helps someone in the Magento community.
While this may technically work, the code you have written is just about the last way you should do this.
In Magento, you really should be using the models provided by the code and not write database queries on your own.
In your case, if you need to update attributes for 1 or many products, there is a way for you to do that very quickly (and pretty safely).
If you look in: /app/code/core/Mage/Adminhtml/controllers/Catalog/Product/Action/AttributeController.php you will find that this controller is dedicated to updating multiple products quickly.
If you look in the saveAction() function you will find the following line of code:
Mage::getSingleton('catalog/product_action')
->updateAttributes($this->_getHelper()->getProductIds(), $attributesData, $storeId);
This code is responsible for updating all the product IDs you want, only the changed attributes for any single store at a time.
The first parameter is basically an array of Product IDs. If you only want to update a single product, just put it in an array.
The second parameter is an array that contains the attributes you want to update for the given products. For example if you wanted to update price to $10 and weight to 5, you would pass the following array:
array('price' => 10.00, 'weight' => 5)
Then finally, the third and final attribute is the store ID you want these updates to happen to. Most likely this number will either be 1 or 0.
I would play around with this function call and use this instead of writing and maintaining your own database queries.
General Update Query will be like:
UPDATE
catalog_product_entity_[backend_type] cpex
SET
cpex.value = ?
WHERE cpex.attribute_id = ?
AND cpex.entity_id = ?
In order to find the [backend_type] associated with the attribute:
SELECT
  backend_type
FROM
  eav_attribute
WHERE entity_type_id =
  (SELECT
    entity_type_id
  FROM
    eav_entity_type
  WHERE entity_type_code = 'catalog_product')
AND attribute_id = ?
You can get more info from the following blog article:
http://www.blog.magepsycho.com/magento-eav-structure-role-of-eav_attributes-backend_type-field/
Hope this helps you.

Adding more information to Magento packingslip or Invoice PDF

How can i add additional information to the Magento Packingslip PDF. I am using integrated label paper, so i would like to add repeat the customers delivery address at the footer and also, some details like total quantity of items in the order and the cost of the items in the order. I am currently modifying local files in: Mage/Sales/Model/Order/Pdf/ but I have only managed to change to font.
EDIT:
Okay, i have made good progress and added most of the information i need, however, i have now stumbled across a problem.. I would like to get the total weight of all items in each order and Quantity.
I am using this code in the shipment.php:
Under the foreach (This is useful in case an order has a split delivery - as you can have one order with multiple "shipments" So this is why I have the code after here:
foreach ($shipment->getAllItems() as $item){
if ($item->getOrderItem()->getParentItem()) {
continue;
...
then I have this further down:
$shippingweight=0;
$shippingweight= $item->getWeight()*$item->getQty();
$page->drawText($shippingweight . Mage::helper('sales'), $x, $y, 'UTF-8');
This is great for Row totals. But not for the whole shipment. What I need to do is have this bit of code "added up" for each item in the shipment to create the total Weight of the whole shipment. It is very important that I only have the total of the shipment - not the order as I will be splitting shipments.
Almost at the end of getPdf(...) in Mage_Sales_Model_Order_Pdf_Shipment you will find the code that inserts new pages (lines 93-94 in 1.5.0.1)
if($this->y<15)
$page = $this->newPage(....)
happening in a for-loop.
You can change the logic here to make it change page earlier to make room for your extra information and then add it before the page shift. You should also place code after the for-block if you want it to appear on the last page as well.
Note: You should not change files in app/code/code/Mage directly. Instead, you should place your changed files under app/code/local/Mage using the same folder structure. That way your changes won't accidently get overwritten in an upgrade.
This should get you the total number of items in your order (there might be a faster way but don't know it off the top of my head):
$quote = Mage::getModel('sales/quote')->load($order->getQuoteId());
$itemsCount = $quote->getItemsSummaryQty();
For invoice PDF
at app/code/local/Mage/Sales/Model/Order/Pdf/Items/Invoice/Default.php
add this before $lineBlock
$product = Mage::getModel('catalog/product')->loadByAttribute('sku', $this->getSku($item), array('weight'));
$lines[][] = array(
'text' => 'Weight: '. $product->getData('weight')*1 .'Kg/ea. Total: ' .$product->getData('weight')*$item->getQty() . 'Kg',
'feed' => 400
);