Magento - Implementing order limits based on custom criterias - php

I'd like to implement a global order limit on certain products. The point of this is that I want to enable backorders on certain products and define several date periods where there are limits to how many of these individual products that may be ordered.
Currently my custom model is loaded with the relevant information for the chosen date period and attached to the product models when they are loaded as $product->setMyModel(...) on these events:
catalog_product_load_after
catalog_product_collection_load_after
sales_quote_item_collection_products_after_load
Accessing my model with data for a specific product is as simple as calling $product->getMyModel(), which I hence will refer to as simply my model.
This is what I want to do:
1. Whenever a product is added to a cart/quote, or placed in an order, I want to do something like this (pseudocode):
// Somehow get $product and $requestedQty (most likely from an event)
$myModel = $product->getMyModel();
if($myModel->applyOrderLimit()) {
// ($orderedQty + $requestedQty) <= $orderLimit
if($myModel->isRequestedQtyAvailable($requestedQty)) {
// Issue an error and prevent the item from being ordered
return;
}
// $orderedQty += $requestedQty
$myModel->addToQtyOrdered($requestedQty);
}
// Continue Magentos default behaviour
1.1. I suspect that Mage_CatalogInventory_Item::checkQuoteItemQty() should be overriden to capture the $requestedQty here.
2. Update $myModel::ordered_qty whenever an order is cancelled, refunded or such.
I guess the real question is where do I run this code, and is there anything more to implementing such an order limit and keeping track of the qty's than I have realized?
To me, this seem like quite a complex task. Which is why I need assistance from more experienced Magento developers!
Note: I couldnt figure out how to mix numbered lists and code blocks, but I hope its readable enough

You don't have to resort to rewriting the Mage_CatalogInventory_Model_Stock_Item:.checkQty() method in order to achieve your goal.
If you add an event observer to the event sales_quote_item_qty_set_after, your observer will be triggered in addition to the cataloginventory check.
public function salesQuoteItemQtySetAfter(Varien_Event_Observer $observer)
{
$quoteItem = $observer->getItem();
$qty = $quoteItem->getQty();
$myModel = $quoteItem->getProduct()->getMyModel()
// Your Logic
// If not salable set error for the quote item
$quoteItem->addErrorInfo(
'mymodule', // origin code
'currently_not_salable', // error code
'The Error Message'
);
}
The sales_quote_item_qty_set_after event is also used by the cataloginventory module to call checkQty(), so you can also examine Mage_CatalogInventory_Model_Observer::checkQuoteItemQty() for additional possibilities on what functionality is available.

Related

Updating data after backend action TYPO3 11.5

Working on Typo3 11.5.13
I'm trying to update some data on my pages table after a be_user changed something.
I read something about setting hooks for that purpose but I can't seem to find a good explanation as to how hooks actually function within Typo3 and how to configure one, especially for my purpose.
As far as I can see, this problem I have should be quickly solved but the complexity of the typo3 doc is hindering my progress again. Maybe you can explain how I can accomplish my goal.
Simply put: A backend user is supposed to choose a date in a datepicker and some dateinterval in the settings of a page. After saving(Or even after picking both values) I would like to update the "Next time happening" field the user can see but not change to be updated to the given date plus the dateinterval chosen.
If you have some sort of idea please share it with me.
Generally hooks are not that good documented. Modern Events are easier to find and better commented. However, if I get your use case right, using DataHandler Hooks are they way to go. That mean, every place which are using the DataHandler to save data are then covered. The backend form engine are using DataHandler.
Basic information about hooks in the core documentation:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Hooks/Index.html
How to identify or find hooks, events, signalslots (depending on TYPO3 version):
https://usetypo3.com/signals-and-hooks-in-typo3.html
https://daniel-siepmann.de/posts/migrated/how-to-find-hooks-in-typo3.html
Introduction or "DataHandler" explained:
https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Typo3CoreEngine/Database/Index.html
Basicly, DataHandler has two main kind of processings:
Data manipulations -> process_datamap()
Actions (move,delete, copy, translate) -> process_cmdmap()
For DataHandler, you register a class only for datamap and/or processmap, not for a concrete hook itself.
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {}
// <your-ext>/ext_localconf.php
// -> for cmdmap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
// -> for datamap hooks
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['yourextname']
= \Vendor\YourExt\Hooks\MyDataHandlerHooks::class;
You need to register your class only for these kind of hooks you want to consume. And you do not have to implement all hooks.
Hooks can be looked up in \TYPO3\CMS\Core\DataHandling\DataHandler (as hooks are normally searched.
Next step would be to find the proper hook for your use case, and simply add that hook method to your class. Naming the hooks are not chooseable for DataHandler hooks.
TYPO3 Core tests contains a test fixture class for DataHandler hooks - which is not complete, but contains at least the most common ones (along with the needed method signatures) since 8.x:
https://github.com/TYPO3/typo3/blob/main/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php
So you may have to look into the version for your core version to get a feeling how the signature should look for that core version.
Generally I would guess one of these two:
processDatamap_postProcessFieldArray(): Hook with prepared field array, and you can simple add your new stuff to write or update it and it will be saved. Good if you need to change the record directly.
processDatamap_afterDatabaseOperations(): Hook after record has been changed. This is a good startpoint if you need to do other things after saving a record.
Given your usecase, I would tip on the first one, so here a example implementation (in the class and registering as datamap hook as explained above):
// <your-ext>/Classes/Hooks/MyDataHandlerHooks.php
namespace <Vendor>\<YourExt>\Hooks;
class MyDataHandlerHooks {
/**
* #param string|int $id
*/
public function processDatamap_postProcessFieldArray(
string $status, // Status of the current operation, 'new' or 'update'
string $table, // The table currently processing data for
$id, // The record uid currently processing data for,
// [integer] or [string] (like 'NEW...')
array &$fieldArray, // The field array of a record, cleaned to only
// 'to-be-changed' values. Needs to be &$fieldArray to be considered reference.
DataHandler $dataHandler
): void
{
// $fieldArray may be stripped down to only the real fields which
// needs to be updated, mainly for $status === 'update'. So if you
// need to be sure to have correct data you may have to retrieve
// the record to get the current value, if not provided as with new
// value.
if ($table === 'be_users'
&& $status === 'update'
&& array_key_exists('target_field_name', $fieldArray)
) {
$valueToReactTo = $fieldArray['target_field_name'];
if ($valueToReactTo === 'some-check-value') {
// needs not to be there
$fieldArray['update-field'] = 'my-custom-value';
}
}
}
}

How do I inject behaviour into an aggregate?

I’m working on an aggregate where certain behaviours can be performed by multiple roles within the application. But before that happens fairly complex validation occurs. It’s this validation that differs per role. Typically it means different configuration settings are checked to determine if the action can be performed.
So, as an example: lets say i have an Order to which i can add OrderLines. If i have role Employee i might be allowed to order up to € 100,- and if i have role Purchaser i might be allowed to order up to € 1000,-.
You could solve this by providing the user instance to the addOrderLine method but that leaks the user context into the ordering context. The next logical thing, and this is what I’ve been doing, is in to inject that validation logic into the method call. I’m calling those methods policies and instantiate the right policy in the application service as i have the relevant user info available there:
<?php
class Order {
public function addItem(OrderPolicy $policy, Item $item, int $amount) {
if (!$policy->canPurchase($item->getPrice() * $amount))
throw new LimitExceededException();
/* add item here */
}
class OrderService {
public function addItem(User $user, $orderId, $itemId, int $amount) {
$order = $this->orderRepo->getForUser($user, $orderId);
$item = $this->itemRepo->get($itemId);
$policy = $this->getOrderPolicyFor($user);
$order->addItem($policy, $item, $amount);
}
}
class PurchaserOrderPolicy
{
function canPurchase($amount) {
return ($amount <= 1000);
}
}
This seems fine, but now it seems to me my ordering context has logic based on user roles (the policy classes) its not supposed to know about.
Does DDD offer any other ways of dealing with this? Maybe Specification? Does this seem fine? Where do the policy classes belong?
It seems that you have two subdomains/systems involved here: ordering system and buying policies system. You need to keep thing separated, your gut was correct. This means that the validation about the maximum order value is checked in an eventual consistent manner relative to the actual item adding. You could do this before (you try to prevent an invalid order) or after (you try to recover from an invalid order).
If you do it before then the application service could orchestrate this and reject the order.
If you do it after then you could implement this as a process and have a ApplyPoliciesToOrdersSaga that listen to the ItemWasAddedToTheOrder event (if you have an event-driven architecture) or that runs as a scheduled task/cron job and checks all orders against the policies (in a classical architecture).

How prevent PrestaShop from updating product quantities after order validation

I am new to prestashop and I am trying to make a payment module where I need to duplicate an order for statistical issues. My problem is that the duplicate order also substracts from product stock and I need to know where, after an order validation, does prestashop update stock to avoid calling the corresponding function. In few words, I call validateOrder() twice but I need that StockAvailable be updated once.
By the way, I examined the whole validateOrder() function searching for the update section / function but I haven't been able to find it.
The only related code that I have been able to find was this:
// updates stock in shops
if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
{
$product_list = $order->getProducts();
foreach ($product_list as $product)
{
// if the available quantities depends on the physical stock
if (StockAvailable::dependsOnStock($product['product_id']))
{
// synchronizes
StockAvailable::synchronize($product['product_id'], $order->id_shop);
}
}
}
but this is only works when advanced stock management is enabled.
Thanks.
The code you're looking for is located in the OrderDetail::create method which uses OrderDetail::checkProductStock which in turn calls StockAvailable::updateQuantity
$update_quantity = StockAvailable::updateQuantity(...
What's interesting is that before updating the quantity there's this condition
if (!StockAvailable::dependsOnStock($product['id_product']))
I suggest that you override this method and return true when you need to.
You might set a global flag before duplicating the order and then check that flag and if it's true return true to prevent updating the stock.
the override code in override/classes/stock/StockAvailable.php
class StockAvailable extends StockAvailableCore
{
public static function dependsOnStock($id_product, $id_shop = null)
{
$no_quantity_update = isset($GLOBALS['no_quantity_update']) && $GLOBALS['no_quantity_update'];
if ($no_quantity_update)
return true;
else return parent::dependsOnStock($id_product, $id_shop = null);
}
}
To make this override effective, delete the file cache/class_index.php to refresh the overrides list
Your module code:
//set to false just to be sure
$GLOBALS['no_quantity_update'] = false;
$this->module->validateOrder($cart->id, Configuration...
//set to true to prevent quantity update
$GLOBALS['no_quantity_update'] = true;
$this->module->validateOrder($cart->id, Configuration...
You can modify directly on the core code but that's not recommended

Call new php function when order is complete in Magento

I have a magento site for ecommerce. When an order is placed, I need to call another function I've created in a new php file and pass the order skus, quantities and shipping address to. I'm extremely comfortable with php, but Magento is an entirely new beast for me.
Does anyone know how to call a function when an order is placed? Even just the name of the event would be helpful.
I haven't used it personally, but sales_order_place_after sounds like it might be what you're looking for. It's used in this way in this Inchoo article, which also involves doing some things as soon as an order is placed.
Here's a page on the Magento wiki about setting up an event observer, which really is just a little XML to tell Magento to run some code when that event is dispatched, and the code you want to run.
you can try sales_order_place_before and sales_order_place_after
if you are interested in the events fired, a common approach is to temporary add
Mage::log($name); in the Mage.php (app/Mage.php) like this
public static function dispatchEvent($name, array $data = array())
{
Mage::log($name);
Varien_Profiler::start('DISPATCH EVENT:'.$name);
$result = self::app()->dispatchEvent($name, $data);
Varien_Profiler::stop('DISPATCH EVENT:'.$name);
return $result;
}
this will log any event fired during a page view or action to the var/log/system.log, if you enabled logging in the backend System->Configuration>Developer->Log Settings

Magento: setCouponCode does not seem to apply coupon on quote model

we are using Magento mainly for transactions and have rewritten the frontend entirely through a custom application. I am having trouble applying a coupon code (shopping cart price rule) on a quote object. The coupon code seems to be rejected - setCouponCode does not return any error, but getCouponCode returns empty string.
I have verified that the coupon code is valid by making a transacting through the admin backend. Here is the code snippet below.
Can someone help me with getting the quote model object to accept and apply a coupon code?
function add_coupon($shoppingCartId, $couponcode) {
try {
$quoteObj = Mage::getModel('sales/quote')->load($shoppingCartId);
$quoteObj->getShippingAddress()->setCollectShippingRates(true);
$quoteObj->getShippingAddress()->setCouponCode($coupon)
->setTotalsCollectedFlag(true)
->collectTotals()
->save();
} catch (Exception $e) {
return array("status"=>"failed", "message"=>"Error applying coupon.");
}
if ($coupon) {
if (!$coupon == $quoteObj->getCouponCode()) {
return array("status"=>"failed", "message"=>"Coupon code is not valid.");
}
}
return array("status"=>"success");
}
I ran into the same issue, and discovered that I needed to call setCouponCode() before adding any items to my quote.
In your case, that would look like:
$quoteObj = Mage::getModel('sales/quote')->setCouponCode($coupon)->load($shoppingCartId);
All logic needed for dealing with coupons is in SalesRule module.
There is model Mage_SalesRule_Model_Coupon which is the coupon object, and it has a resource model Mage_SalesRule_Model_Mysql4_Coupon.
To create a new coupon you could instantiate the above coupon model, fill all fields and call save() method. It will write data to table salesrule_coupon. But if you'll look at this table you'll see that coupons depend on rule id, so you need to create some sales rule first.
Mage_SalesRule_Model_Rule cares about rules, it also has own resource model. I think it will be helpful for you to investigate how they are works
I lost a few hours to this tonight. Hopefully I can save someone the same.
Emily is correct for this example (where you load the shopping cart in to a quote).
This applies to Magento 1.7.0.2, I'm not sure if it holds for other versions.
If you are programmatically creating an order but you are not using the shopping cart to do so this may help you:
The underlying problem is that Magento is running collectTotals() each time you add/update an address on the quote - and here's the important part: it caches them. (In fact, after you save an address if you check the getTotalsCollectedFlag you'll see it's set to true!). Setting the coupon code after this, even if you run collectTotals(), doesn't apply the coupon code.
You could call setTotalsCollectedFlag(false) on the quote then apply the coupon, but this is a bad idea (it could cause some miscalculations to occur according to a Magento bug report that is no longer accessible), what you want to do is call setCouponCode before you set any addresses (or at the very least, before you set the last address).

Categories