Set default combination automaticly based on stock Prestashop 1.7 - prestashop

i need Prestashop to:
Check stock of combinations from a product.
If a combination is out of stock, set default combination to a different combination which is in stock.
This way the shop will not show : 'out of stock' at the product, on the category listing. Makes no sense, because the product is not out of stock, only 1 combination is out of stock.
Another solution will be: the out of stock sticker to check if there are any combinations in stock.
Please do not advise any modules.
Example given:
T-shirt in size Small, Medium and Large.
Small: 0 stock, Medium: 10 stock, Large: 10 stock.
Prestashop will now show T-shirt as out of stock on the frontend, unless i manually check Medium or Large as a default size.

You will need to modify ProductController(/controllers/front/ProductController.php)
Basically what i did was to check if the default attribute has stock. If there is stock,then there is no need to look else where.
If there is no stock, then get all the product attributes and loop through the attributes. Once attribute with stock is found, delete the old default attribute and set the found as default attribute.
Add this methods inside the productController Class
/*
start set default based on stock
*/
public function getProductAttributes()
{
$sql = "SELECT * FROM " . _DB_PREFIX_ . "product_attribute WHERE
id_product = ". (int) $this->product->id;
return Db::getInstance()->executeS($sql);
}
public function getProductStock($id_product_attribute)
{
return StockAvailable::getQuantityAvailableByProduct($this->product->id,
$id_product_attribute, $this->context->shop->id);
}
public function checkDefault()
{
$id_product_attribute = Product::getDefaultAttribute($this->product->id);
$stk = (int) $this->getProductStock($id_product_attribute);
if( $stk > 0){
return true;
}
return false;
}
public function checkAttributeStock( $id_product_attribute)
{
$stk = $this->getProductStock($id_product_attribute);
if( $stk > 0){
return true;
}
return false;
}
public function setDefaultBasedOnStock()
{
if($this->checkDefault()){
return true;
}
$product_atts = $this->getProductAttributes();
if(count($product_atts) >0){
foreach($product_atts as $attri){
if($this->checkAttributeStock($attri['id_product_attribute'])){
$this->product->deleteDefaultAttributes();
$this->product->setDefaultAttribute($attri['id_product_attribute']);
break;
}
}
}
}
/*
end set default based on stock
*/
Then you need to call setDefaultBasedOnStock method inside init Method just after parent::init();
public function init()
{
parent::init();
$this->setDefaultBasedOnStock();
// here is the rest of init code ......
//Do not delete the rest of init code
}
Please do not delete the rest of init() method code, just add this line $this->setDefaultBasedOnStock(); below parent::init();
The best way will be to override productController
Instead of modifying productController class, you can create productController override class inside /override/controllers/front/ folder
class ProductController extends ProductControllerCore {
// add all the methods above
// including the init method
}
// After add override you may need to clear cache if prestashop cache is enabled.

Related

Shopware 6: Tax Free with domestic delivery in DE (TaxDecorator & CartRuleLoader)

Some customers with a domestic shipping address within Germany are still tax exempt (e.g. NATO, or US Army).
Since this is considered an export delivery.
How can this be implemented? The only solution I have found is to completely override/replace CartRuleLoader
What I have done so far: I created a custom field in Customers, where a store editor can specify a customer as tax free (even within Germany).
Then I overwrote the class TaxDetector:
class TaxDetector extends \Shopware\Core\Checkout\Cart\Tax\TaxDetector
{
public function isNetDelivery(SalesChannelContext $context): bool
{
$customer = $context->getCustomer();
if ($customer && ($cf = $customer->getCustomFields()) && !empty($cf['mycustomfield']) && $cf['mycustomfield'] === 'nato') {
return true;
}
return parent::isNetDelivery($context);
}
public function getTaxState(SalesChannelContext $context): string
{
$customer = $context->getCustomer();
if ($customer && ($cf = $customer->getCustomFields()) && !empty($cf['mycustomfield']) && $cf['mycustomfield'] === 'nato') {
return CartPrice::TAX_STATE_FREE;
}
return parent::getTaxState($context);
}
}
The problem is that Shopware 6 checks the validity of the tax status in CartRuleLoader when completing an order. And here the test is done in private functions that I cannot override.
load -> validateTaxFree -> detectTaxType
Any suggestions on how to solve this more eligant and futerprove then override all CartRuleLoader?
This is indeed a little tricky.
When you look at the method CartRuleLoader::detectTaxType you can find these lines:
$currency = $context->getCurrency();
$currencyTaxFreeAmount = $currency->getTaxFreeFrom();
$isReachedCurrencyTaxFreeAmount = $currencyTaxFreeAmount > 0 && $cartNetAmount >= $currencyTaxFreeAmount;
if ($isReachedCurrencyTaxFreeAmount) {
return CartPrice::TAX_STATE_FREE;
}
It's arguably a little hacky but you could "trick" Shopware to return the tax free state by setting the tax free amount of the currency to a minimal amount.
Therefore you could decorate Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory.
<service id="MyPlugin\Core\System\SalesChannel\Context\SalesChannelContextFactoryDecorator" decorates="Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory" decoration-priority="-999">
<argument type="service" id="MyPlugin\Core\System\SalesChannel\Context\SalesChannelContextFactoryDecorator.inner"/>
</service>
In the decorator you could then have your logic and set the tax free amount:
<?php declare(strict_types=1);
namespace MyPlugin\Core\System\SalesChannel\Context;
use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice;
use Shopware\Core\System\SalesChannel\Context\AbstractSalesChannelContextFactory;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
class SalesChannelContextFactoryDecorator extends AbstractSalesChannelContextFactory
{
private AbstractSalesChannelContextFactory $decorated;
public function __construct(
AbstractSalesChannelContextFactory $decorated,
) {
$this->decorated = $decorated;
}
public function getDecorated(): AbstractSalesChannelContextFactory
{
return $this->decorated;
}
public function create(string $token, string $salesChannelId, array $options = []): SalesChannelContext
{
$context = $this->getDecorated()->create($token, $salesChannelId, $options);
// ... your logic
if (...) {
$context->getCurrency()->setTaxFreeFrom(0.00001);
$context->setTaxState(CartPrice::TAX_STATE_FREE);
}
return $context;
}
}
Edit: It looks like this could not be enough as there is a security check which checks if the previous state wasn't tax free and resets it otherwise.
if ($previous !== CartPrice::TAX_STATE_FREE) {
$context->setTaxState($previous);
}
You might have to additionally set the tax state inside your decorator (updated the above example). The tax free amount would then make it stick.

Prestashop hookActionCartSave function run 2 times?

I created a presta module and use the actionCartSave hook.
But when I print string in hookActionCartSave function, it display double result
I dont know why, can you explain for me that?
My code:
public function hookActionCartSave()
{
if (!$this->active || !Validate::isLoadedObject($this->context->cart) || !Tools::getIsset('id_product')) return;
print_r('expression');
}
The result is:
expressionexpression
Thank you
The hook ActionCartSave is called in add and update method of Cart class.
public function add($autodate = true, $null_values = false)
{
/* ... */
Hook::exec('actionCartSave');
return $return;
}
public function update($null_values = false)
{
/* ... */
Hook::exec('actionCartSave');
return $return;
}
So if you search in various controller you discover that the cart is saved more times, so the hook is called more of one times :)

Replacing a series of if statements with an interface-based approach

so I am trying to write a discounts method that will apply discount(s) on a product.
The current vanilla code goes like so:
void ApplyDiscount(List<DiscountRule> discountRules, Product objProduct)
{
foreach (var discountRule in discountRules)
{
// this is a very simple way of deciding on the available discounts
if (discountRule.Type==DiscountType.Percent)
{
// Process for percentage discount
}
if (discountRule.Type==DiscountType.Free)
{
// Process for flat discount
}
// and so on , there are like 5 more types,
// not mentioned here for the case of brevity.
}
}
What this method does is take a list of discount rules and apply on the product.
The discount rules are fetched by executing a SP # the server and that returns the
available discounts for that product.
The code review for this resulted in the following comment:
Please use an interface based approach and try to get rid of the IFs!
I can get rid of the IFs but they will be replaced by SWITCH.
How do I go about using an interface?
May be this question is not constructive, but I would want to know if some OOPs gurus here can guide me in writing this better.
Regards.
An interface / virtual-dispatch approach might look something like this:
// First we loosely define how a "discount" can be used.
// This could also be an abstract class, if common base-class
// functionality is desired.
public interface IDiscount
{
// This is called to apply this discount to a particular product
void ApplyDiscount(Product product);
}
// Here's one implementation that applies a percentage discount
public class PercentDiscount : IDiscount
{
private decimal m_percent;
public PercentDiscount(decimal percent) {
m_percent = percent;
}
#region IDiscount implementation
public void ApplyDiscount(Product product) {
product.Price -= product.Price * m_discount;
}
#endregion
}
// Here's another implementation that makes a product free
public class FreeDiscount : IDiscount
{
public FreeDiscount() {
}
#region IDiscount implementation
public void ApplyDiscount(Product product) {
product.Price = 0;
}
#endregion
}
public class SomeClass {
// Now applying the discounts becomes much simpler! Note that this function
// takes a collection of IDiscounts, and applies them in a consistent way,
// by just calling IDiscount.ApplyDiscount()
void ApplyDiscounts(IEnumerable<IDiscount> discounts, Product product) {
foreach (var discount in discounts) {
discount.ApplyDiscount(product);
}
}
}
Note that I also changed ApplyDiscounts to take an IEnumerable<T> instead of List<T>. This allows any arbitrary collection type to be passed, and also doesn't allow the function to inadvertently modify the collection.

How can i get current product inside a hook?

How can i get the current product inside hookdisplayTop ?
This is the current way i'm making sure i'm in the "product" page :
if ( Dispatcher::getInstance()->getController() == "product") {
//i'm in the product page
}
If you're in the ProductController then you can always get the product_id of the current product without resorting to an override (these can quickly get out of hand if overused and should be a last resort):
if ($id_product = (int)Tools::getValue('id_product'))
$product = new Product($id_product,
true,
$this->context->language->id,
$this->context->shop->id);
if (!Validate:: isLoadedObject($product))
return;
Solved :
class ProductController extends ProductControllerCore
{
public function getProduct()
{
return $this->product;
}
}
Created an override of "ProductController" (code above)
In the hook just call : $this->context->controller->getProduct()

CoolStorage can't set One-To-One relationship

The issue is that I can't understand how to make a OneToOne relation between two objects the way for the first object to have a link to the second and for the second to have a link to the first. Here's the code:
[MapTo("Model")]
public class Model : CSObject<Model, int>
{
[OneToOne(LocalKey = "ModelID", ForeignKey = "ModelID")]
public Product Product { get { return (Product)GetField ("Product"); } set { SetField ("Product", value); } }
}
[MapTo("Product")]
public class Product : CSObject<Product, int>
{
[OneToOne(LocalKey = "ProductID", ForeignKey = "ProductID")]
public Model Model { get { return (Model)GetField ("Model"); } set { SetField ("Model", value); } }
}
The thing is that when I create a product and a model and set the model's property "Product" to the created one and save it, the product's "Model" property doesn't get set and remains NULL. I've already tried making all the local and foreign keys for both Product's and Model's properties the same (e.g. "ModelID") but it didn't solve the problem. What is the right way of doing this?
I guess making one of them [OneToMany] will do the trick but will return a collection while I need a single object to be returned by a property.
Update
Here comes a simple solution one would call a crutch:
[OneToMany]
public CSList<Product> _ProductList { get { return (CSList<Product>)GetField ("_ProductList"); } set { SetField ("_ProductList", value); } }
[NotMapped]
public Product Product {
get {
CSList<Product> list = this._ProductList;
if (list.Count > 0)
return list [0];
return null;
}
set {
if (value != null) {
CSList<Product> list = this._ProductList;
list.RemoveAll ();
list.Add (value);
}
}
}
You can make both relations [ManyToOne]. That will work in your scenario.