Jump to content

Order Validation process


Serial

Recommended Posts

The best way of handling these are to use the hook functionality and add your code in a module attached to the correct function in your case:

function actionValidateOrder(Array $params)
{
   $id_lang = (int)Context::getContext()->language->id;
   $currency = $params['currency'];
   $order = $params['order'];
   $customer = $params['customer'];
   $delivery = new Address((int)$order->id_address_delivery);
   $invoice = new Address((int)$order->id_address_invoice);
   $order_date_text = Tools::displayDate($order->date_add, $id_lang);
   $carrier = new Carrier((int)$order->id_carrier);
   $message = $order->getFirstMessage();

   // Do something cool here to insert your data

}

Conveniently there's also:

hookdisplayAdminOrder(Array $params)
{
   if (isset($params['id_order']))
      $order = new Order($params['id_order']);
   if ($order)
   {
      // Fetch your data and display it by returning the html directly or via a template 
   }
}
Edited by Paul C (see edit history)
Link to comment
Share on other sites

Hi,

 

I would like to know where Prestashop execute the SQL query that created a new order row in orders table (what file and function) ?

Because I want to fill a field that I have created in this table.

 

Thanks !

this is done in root/classes/PaymentModule.php class's validateOrder method.

but if you want to implement this on your module then you have to override that method in your module first then only it will allow.

otherwise you can change the same file as well for your store.

Link to comment
Share on other sites

 

The best way of handling these are to use the hook functionality and add your code in a module attached to the correct function in your case:

function actionValidateOrder(Array $params)
{
   $id_lang = (int)Context::getContext()->language->id;
   $currency = $params['currency'];
   $order = $params['order'];
   $customer = $params['customer'];
   $delivery = new Address((int)$order->id_address_delivery);
   $invoice = new Address((int)$order->id_address_invoice);
   $order_date_text = Tools::displayDate($order->date_add, $id_lang);
   $carrier = new Carrier((int)$order->id_carrier);
   $message = $order->getFirstMessage();

   // Do something cool here to insert your data

}

 

It's that I want :) What is the file to find this function (actionValidateOrder) ?

 

How to do a module for this ? (please, i'm very bad in PHP ^^)

 

I have created a list in order-carrier.tpl :

{if $option.unique_carrier}
  <!-- Ajout liste déroulante pour sélectionner magasin seulement si Retrait en magasin -->
	{if $carrier.instance->id=="13"}
	<strong>{$carrier.instance->name|escape:'htmlall':'UTF-8'}</strong>
	<br/>{l s='Veuillez choisir le magasin :'} 
          <select name="choix_magasin" id="choixmag">
	     <option value="0" select="selected">--</option>
		{foreach $stores as $store}
			<option value="{$store.id_store}">{$store.city}</option>
		{/foreach}
	</select>							
{/if}
<!-- Jusqu'ici -->

I have override Order-Controller.php

<?php

class OrderController extends OrderControllerCore
{
	public function get_ListeMagasins() 
	{
	 $stores = Db::getInstance()->executeS('
		SELECT s.*, cl.name country, st.iso_code state
		FROM '._DB_PREFIX_.'store s
		'.Shop::addSqlAssociation('store', 's').'
		LEFT JOIN '._DB_PREFIX_.'country_lang cl ON (cl.id_country = s.id_country)
		LEFT JOIN '._DB_PREFIX_.'state st ON (st.id_state = s.id_state)
		WHERE s.active = 1 AND cl.id_lang = '.(int)$this->context->language->id);
		$this->context->smarty->assign('stores',$stores);

		return $stores;
	}

	public function get_ChoixMagasin()
	{
		global $smarty;
		$retrait = $this->context->cart->id_carrier;

		if ($retrait == 13) /* Si le client a choisi le retrait en magasin (id : 13) */
		{
			$resultat = Tools::getValue('choix_magasin');
			$smarty->assign('choix_magasin', $resultat);
		}
		else /* Sinon on met la variable a 0 */
		{
			$resultat=0;
			$smarty->assign('choix_magasin', $resultat);
		}
	}
}

Directly in OrderController.php (not in override because I don't know how we do this) :

case OrderController::STEP_DELIVERY:
	if (Tools::isSubmit('processAddress'))
		$this->processAddress();
	$this->autoStep();
	$this->_assignCarrier();
	$this->get_ListeMagasins(); /* Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison */
	$this->setTemplate(_PS_THEME_DIR_.'order-carrier.tpl');
break;
if ($is_advanced_payment_api === true)
	$this->_assignAddress();

        // assign some informations to display cart
	$this->_assignSummaryInformations();
        $this->get_ChoixMagasin();
        $this->setTemplate(_PS_THEME_DIR_.'order-payment.tpl');
break;

And i created 2 fields :  id_store  and name_store in orders table.

 

So, in fact, I want to register my smarty variable "$choix_magasin" just above in the new field id_store.

 

Can you put me on the way ?

It is the first time when I create a module ^^

 

Thanks a lot :)

Edited by Serial (see edit history)
Link to comment
Share on other sites

Ok. Obviously I'm going to have to try and do this blind with no testing but here goes!

 

The first "hack" to `OrderController.php` can be replaced with the following function in your `OrderController.php` override:

public function initContent()
{
   if ((int)$this->step === OrderController::STEP_DELIVERY)
     $this->get_ListeMagasins(); // Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison
   parent::initContent();
}

The above will get the list of stores available to your modified order-carrier.tpl template but we need to do something with that information to make it accessible to us later. It doesn't look like you need the store choice in order-payment.tpl so if not we can just extend the above to cover both "hacks":

public function initContent()
{
   if ((int)$this->step === OrderController::STEP_DELIVERY)
     $this->get_ListeMagasins(); // Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison
   else if ((int)$this->step === OrderController::STEP_PAYMENT)
     $this->get_ChoixMagasin();

   parent::initContent();
}

You don't appear to actually do anything with this stored value though except save it in a smarty variable? In order to access it in later steps you're going to need to either save this data or postpone saving it by adding it as a hidden field in order-payment.tpl. That could be tricky as you're basically handing over control to the payment modules at that point.

 

This is where you need to make a design choice ;)

 

All during the above processing we don't actually have an order - just a cart object. You could save the choice in the cart object but that would mean extending the cart class to add your additional property (id_store). You would still then need to add additional code to extend the order object as well (or modify it to access the data in the cart object in order to save it).

 

Another option would be to store the data manually either in its own table or in the cart table. (a table of id_cart and id_store would be sufficient).

 

I'm not 100% sure what you're trying to achieve though so it's hard at this point to advise one way or another! What is the store choice actually for?

Link to comment
Share on other sites

Ok. Obviously I'm going to have to try and do this blind with no testing but here goes!

 

The first "hack" to `OrderController.php` can be replaced with the following function in your `OrderController.php` override:

public function initContent()
{
   if ((int)$this->step === OrderController::STEP_DELIVERY)
     $this->get_ListeMagasins(); // Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison
   parent::initContent();
}

The above will get the list of stores available to your modified order-carrier.tpl template but we need to do something with that information to make it accessible to us later. It doesn't look like you need the store choice in order-payment.tpl so if not we can just extend the above to cover both "hacks":

public function initContent()
{
   if ((int)$this->step === OrderController::STEP_DELIVERY)
     $this->get_ListeMagasins(); // Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison
   else if ((int)$this->step === OrderController::STEP_PAYMENT)
     $this->get_ChoixMagasin();

   parent::initContent();
}

I understand at this point. I'm going to do this override.

 

Next, I join images for you to have an illustration of my problem.

 

MylistFrontOffice.png : it's my front office when the customer choose his delivery (order-carrier.tpl).

Debug_Order-Payment.tpl.png : it's when I'm in order-payment.tpl with {debug} tool. It's my variable that the customer has choose before in my list. The value is good because i and it's this value that I want to save in my database in ps_orders.id_store.

Orders_table.png : It's my database in ps_orders.

 

My problem is "very easy" to understand. During the delivery choice, if the customer want to take his order at a store, he select the carrier "Retreat in store" and he selects my store in the list of available stores. After, I don't want to display other informations during the cart process. I want just to be able to display his id_store that he choosen in mail confirmation order for me and him, his history of carrier, and also in my back office. I think if this id is in my database, I can to display this information everywhere.

 

I hope you see better what I want to do to help me :)

post-1055497-0-95294900-1440072354_thumb.png

post-1055497-0-70148200-1440072412_thumb.png

post-1055497-0-59215500-1440072790_thumb.png

Edited by Serial (see edit history)
Link to comment
Share on other sites

OK. Is there a specific reason you decided not to just create your own "carrier" that allowed you to choose a store as a delivery point? Prestashop already has the ability to manage multiple stores, including addresses (distinct from multi-shop).

 

I'm asking because I think this would require less modification!

Link to comment
Share on other sites

Yes, but I have 35 stores. And more in the futur.

 

So, if I add each stores that a carrier, I would have around 40 lines at the choice of delivery :wacko:

 

But my carrier "Retreat in store" exist (see mylistFrontOffice.png image). But it seems to me that Prestashop don't permits to choose a store ?

Edited by Serial (see edit history)
Link to comment
Share on other sites

OK, well the next stage should be fairly easy then, since you pretty much have all the information you need. I think the key here is to look at the delivery as being to one of your stores rather than to the customer. I don't think you actually need to save the store id as a separate field - just use it instead of the id of the delivery address.

 

Ideally we should be able to get the id of an address for your store and just use that in $this->context->cart->id_address_delivery ... but of course it isn't that easy as the Store class doesn't use an address object but uses separate fields for the address. The simplest thing to do would be to create a new address object and use the id of that - unfortunately this will create new database entries for each order and clutter up the customer address list but I guess no worse than a standard customer order. We would also have to be careful of any cart or tax rules that depend on the delivery address, but I'm assuming customers who want to collect from store are local anyway.

 

We modify the choice function to just:

public function get_ChoixMagasin()
{
    $retrait = $this->context->cart->id_carrier;

    if ($retrait == 13) /* Si le client a choisi le retrait en magasin (id : 13) */
        return (int)Tools::getValue('choix_magasin');
    return false;
}

and then the override of initContent to:

public function initContent()
{
   $pickup_store = false;

   if ((int)$this->step === OrderController::STEP_DELIVERY)
     $this->get_ListeMagasins(); // Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison
   else if ((int)$this->step === OrderController::STEP_PAYMENT)
     $pickup_store = $this->get_ChoixMagasin();

   parent::initContent();

   if ((int)$this->step === OrderController::STEP_PAYMENT)
     if ($pickup_store)
       $this->_setPickupAddress($pickup_store)
}

Now we need to create a function to set the pickup address (overriding the customer address set in parent::initContent() by using the data from our Store object). Any time you need to display something different for orders that are collected from your store, then you just need to check id_carrier. Be careful though as the carrier id can change!

public function _setPickupAddress($pickup_store)
{
   $store = new Store((int)$pickup_store); // Get the store object
   $deliveryAddress = new Address(); // Create a new address object
   $deliveryAddress->id_customer = $this->context->customer->id;
   $deliveryAddress->id_country = $store->id_country;
   $deliveryAddress->id_state = $store->id_state;
   $deliveryAddress->firstname = 'Pickup from'; // You may need to play with these
   $deliveryAddress->lastname = $store->name; // for it to make sense
   $deliveryAddress->address1 = $store->address1;
   $deliveryAddress->address2 = $store->address2;
   $deliveryAddress->postcode = $store->postcode;
   $deliveryAddress->city = $store->city;
   $deliveryAddress->phone = $store->phone;

   $deliveryAddress->save();

   if (Validate::isLoadedObject($deliveryAddress) && ($deliveryAddress->id_customer == $customer->id))
     $this->context->smarty->assign('delivery', $deliveryAddress);
}
Edited by Paul C (see edit history)
Link to comment
Share on other sites

Hi !

 

I haven't errors in the process but my adress delivery doesn't change :wacko:

 

This is my override with your changes :

<?php

class OrderController extends OrderControllerCore
{
	public function initContent()
	{
		$pickup_store = false;

   		if ((int)$this->step === OrderController::STEP_DELIVERY)
    		$this->get_ListeMagasins(); // Appelle la methode pour recuperer la liste des magasins dans le tableau Livraison
   		else if ((int)$this->step === OrderController::STEP_PAYMENT)
     		$pickup_store = $this->get_ChoixMagasin(); // Enregistre le choix du magasin du client dans la variable $pickup_store

   		parent::initContent();

   		if ((int)$this->step === OrderController::STEP_DELIVERY)
   			if ($pickup_store)
   				$this->_setPickupAddress($pickup_store);
	}

	public function get_ListeMagasins() 
	{
	 	$stores = Db::getInstance()->executeS('
			SELECT s.*, cl.name country, st.iso_code state
			FROM '._DB_PREFIX_.'store s
			'.Shop::addSqlAssociation('store', 's').'
			LEFT JOIN '._DB_PREFIX_.'country_lang cl ON (cl.id_country = s.id_country)
			LEFT JOIN '._DB_PREFIX_.'state st ON (st.id_state = s.id_state)
			WHERE s.active = 1 AND cl.id_lang = '.(int)$this->context->language->id);
			$this->context->smarty->assign('stores',$stores);

		return $stores;
	}

	public function get_ChoixMagasin()
	{
		$retrait = $this->context->cart->id_carrier;

		if ($retrait == 13) /* Si le client a choisi le retrait en magasin (id : 13) */
			return (int)Tools::getValue('choix_magasin');
		return false;
	}

	public function _setPickupAddress($pickup_store)
	{
  		$store = new Store((int)$pickup_store); // Get the store object
   		$deliveryAddress = new Address(); // Create a new address object
   		$deliveryAddress->id_customer = $this->context->customer->id;
   		$deliveryAddress->id_country = $store->id_country;
   		$deliveryAddress->id_state = $store->id_state;
   		$deliveryAddress->firstname = 'Livré à : '; // You may need to play with these
   		$deliveryAddress->lastname = $store->name; // for it to make sense
   		$deliveryAddress->address1 = $store->address1;
  		$deliveryAddress->address2 = $store->address2;
   		$deliveryAddress->postcode = $store->postcode;
   		$deliveryAddress->city = $store->city;
   		$deliveryAddress->phone = $store->phone;

   		$deliveryAddress->save();

   		if (Validate::isLoadedObject($deliveryAddress) && ($deliveryAddress->id_customer == $customer->id))
     		$this->context->smarty->assign('delivery', $deliveryAddress);
	}
}
Edited by Serial (see edit history)
Link to comment
Share on other sites

Ah I think I can see the error - In the initContent() override you have OrderController::STEP_DELIVERY twice. The last one (after the parent::initContent() call) should be OrderController::STEP_PAYMENT. Not saying that it will work but it definitely won't at the moment ;)

 

EDIT: I just noticed another error - A typo on my part! We actually don't need to check the customer id in the last of statement anyway but it was wrong.

    public function _setPickupAddress($pickup_store)
    {
        $store = new Store((int)$pickup_store); // Get the store object
        $deliveryAddress = new Address(); // Create a new address object
        $deliveryAddress->id_customer = $this->context->customer->id;
        $deliveryAddress->id_country = $store->id_country;
        $deliveryAddress->id_state = $store->id_state;
        $deliveryAddress->firstname = 'Livré à : '; // You may need to play with these
        $deliveryAddress->lastname = $store->name; // for it to make sense
        $deliveryAddress->address1 = $store->address1;
        $deliveryAddress->address2 = $store->address2;
        $deliveryAddress->postcode = $store->postcode;
        $deliveryAddress->city = $store->city;
        $deliveryAddress->phone = $store->phone;
 
        $deliveryAddress->save();
 
        if (Validate::isLoadedObject($deliveryAddress))
           $this->context->smarty->assign('delivery', $deliveryAddress);
    }
Edited by Paul C (see edit history)
Link to comment
Share on other sites

Thanks for your correction :)

 

It's me for the first error, sorry ^^

 

I have an other error too after choose delivery :

[PrestaShopException]

Property Address->alias is empty
at line 881 in file classes/ObjectModel.php

876. 
877. 			$message = $this->validateField($field, $this->$field);
878. 			if ($message !== true)
879. 			{
880. 				if ($die)
881. 					throw new PrestaShopException($message);
882. 				return $error_return ? $message : false;
883. 			}
884. 		}
885. 
886. 		return true;

Link to comment
Share on other sites

Yup. I should have checked the required fields for the address object!

 

EDIT: The alias can only be max 32 characters, so if your store names are long you may have to change it to something else e.g. "Pickup"

public function _setPickupAddress($pickup_store)
{
        $store = new Store((int)$pickup_store); // Get the store object
        $deliveryAddress = new Address(); // Create a new address object
        $deliveryAddress->id_customer = $this->context->customer->id;
        $deliveryAddress->id_country = $store->id_country;
        $deliveryAddress->id_state = $store->id_state;
        $deliveryAddress->firstname = 'Livré à : '; // You may need to play with these
        $deliveryAddress->lastname = $store->name; // for it to make sense
        $deliveryAddress->alias = 'Livré à : '.$store->name; // Used in the customer's address list to identify this address
        $deliveryAddress->address1 = $store->address1;
        $deliveryAddress->address2 = $store->address2;
        $deliveryAddress->postcode = $store->postcode;
        $deliveryAddress->city = $store->city;
        $deliveryAddress->phone = $store->phone;
 
        $deliveryAddress->save();
 
        if (Validate::isLoadedObject($deliveryAddress))
         $this->context->smarty->assign('delivery', $deliveryAddress);
}
Edited by Paul C (see edit history)
Link to comment
Share on other sites

Ok for 32 characters. I notice this.

 

An other error in the firstname :( :

[PrestaShopException]

Property Address->firstname is not valid
at line 881 in file classes/ObjectModel.php

876. 
877. 			$message = $this->validateField($field, $this->$field);
878. 			if ($message !== true)
879. 			{
880. 				if ($die)
881. 					throw new PrestaShopException($message);
882. 				return $error_return ? $message : false;
883. 			}
884. 		}
885. 
886. 		return true;


Edited by Serial (see edit history)
Link to comment
Share on other sites

You probably can't have a : in the name. Unfortunately these fields are validated as "names" not just any text... It would make sense to have something relating to your store I guess in place of the first and last names?

$deliveryAddress->firstname = 'Lettersonly'; // You may need to play with these
$deliveryAddress->lastname = 'sameforthis'; // for it to make sense
Edited by Paul C (see edit history)
Link to comment
Share on other sites

Ok, I correct this. The order pass good.

 

But, my delivery address doesn't change. It's always the delivery address of the customer.

So, I can't see the store.

 

EDIT : In my back office, I have new line with the store address. But, we must to go to the list field. (screenshot enclosed). For each orders pass, a new line is created.

We can't set the delivery address by default ? Because, I want the store address in mails order principally.

post-1055497-0-89831100-1440399094_thumb.png

Edited by Serial (see edit history)
Link to comment
Share on other sites

Ah I think I we need to change the address in the Cart object! Doh.

public function _setPickupAddress($pickup_store)
{
        $store = new Store((int)$pickup_store); // Get the store object
        $deliveryAddress = new Address(); // Create a new address object
        $deliveryAddress->id_customer = $this->context->customer->id;
        $deliveryAddress->id_country = $store->id_country;
        $deliveryAddress->id_state = $store->id_state;
        $deliveryAddress->firstname = 'Livré à : '; // You may need to play with these
        $deliveryAddress->lastname = $store->name; // for it to make sense
        $deliveryAddress->alias = 'Livré à : '.$store->name; // Used in the customer's address list to identify this address
        $deliveryAddress->address1 = $store->address1;
        $deliveryAddress->address2 = $store->address2;
        $deliveryAddress->postcode = $store->postcode;
        $deliveryAddress->city = $store->city;
        $deliveryAddress->phone = $store->phone;
 
        $deliveryAddress->save();
 
        if (Validate::isLoadedObject($deliveryAddress))
        {
          $this->context->cart->id_address_delivery = $deliveryAddress->id_address;
          $this->context->smarty->assign('delivery', $deliveryAddress);
        }
}
Link to comment
Share on other sites

Sorry, but id_address is undefined property :(

 

Notice: Undefined property: Address::$id_address in C:\wamp\www\PrestaTDU\override\controllers\OrderController.php on line 64

$this->context->cart->id_address_delivery = $deliveryAddress->id_address;
Link to comment
Share on other sites

It's the same :/

It's not the address of the store but my address who is display.

 

I think it's about this line :

$deliveryAddress = new Address(); // Create a new address object

It creates a new address but no set the current address by the store address. It's not possible to do this ? :

1- Keep his own address somewhere that he entered in the register form

2- Set his address just during the order by store's address

3- After, reset his own address in his profile

Edited by Serial (see edit history)
Link to comment
Share on other sites

The problem with doing it this way is that although the store has an address - it isn't an Address object that we can easily refer to (The Store object should really be consistent and use a "proper" Address object in my opinion but that's an issue for the core developers and changing it could break legacy code).

 

The line :

$this->context->cart->id_address_delivery = $deliveryAddress->id;

Should set the delivery address but for some reason it isn't - it must be being overwritten somewhere later in the process. Changing it on the fly won't work as the Order refers to the Address object (via its id) so it has to be able to find it. It probably doesn't need to be associated with the specific customer but it does have to be "owned" by a customer I think (you could try setting the customer_id field to 0 later though to stop it showing in their address book - once it works as expected).

 

I don't have easy access to the source right now to track down where the address is being changed but will have a look into it once I get the chance.

 

I still think it would be easier if this were done in a custom carrier though. I suspect it would solve a lot of the issues! There are already modules available to do this but I understand if you don't want to pay for one ;)

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...