Prestashop: PS1.7 add Specific Price with time (hours)

Created on 9 Nov 2018  路  19Comments  路  Source: PrestaShop/PrestaShop

Describe the bug
We had multiple shop which was running PS1.6 and PS1.7.4.4. I found that in PS1.6, we can click the from date / time and to date / time in product -> Add a specific price. However, In PS1.7, I found we only can select the from date and to date only, but we have some promotion want start at specific time (e.g: 20% discount start at 15:00 and end at 18:00.

Is it PS1.7 not allow to set specific price with time range?

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'Products' -> 'Edit' -'Price'
  2. Click on 'Add a specific price'
  3. Scroll down to 'Available from and to date' -> Click the calendar
  4. Only allow select the date without time

Screenshots
(https://user-images.githubusercontent.com/38032785/48235897-e0581c00-e3fa-11e8-8360-327653eb30bd.png)

Additionnal information
PrestaShop version: 1.7.3.2 and 1.7.4.4
PHP version: 7.0.31

1.7.4.4 BO Improvement M PM 鉁旓笍 Products To Do UX 鉁旓笍

Most helpful comment

@colinegin yes, we talked about that with @matks

All 19 comments

Hi @bmwjoeytsang,

Thanks for your report.
Yes, you are right.
In the PS1.6, the available date is allowed to set with the time range
image
But not in the 1.7
image
It is an improvement could be added to the PS1.7.
@marionf what do you think?
Thanks!

Yes, I think it will be nice to add it

@colinegin wdyt ?

This is a major regression (you should add that tag), there are legal constraints in France for sales period (next winter sales should start at 8 in the morning for example)

Thank you for your feedback @Benoth , I agree with you it is an important feature that we should add back to specific prices. I need to discuss with the team see if we can add it to our 1.7.6 roadmap, we will keep you updated.

Hi,

New suggestion by @PrestaShark,
/admin/themes/new-theme/public/catalog_product.bundle.js

Change from .datetimepicker({format:"YYYY-MM-DD"})
to .datetimepicker({format:"YYYY-MM-DD HH:mm:ss"})

Thanks!

/src/PrestaShopBundle/Form/Admin/Product/ProductSpecificPrice.php

to

->add(
                'sp_from',
                DatePickerType::class,
                [
                    'required' => false,
                    'label' => $this->translator->trans('Available from', [], 'Admin.Catalog.Feature'),
                    'attr' => ['placeholder' => 'YYYY-MM-DD HH:mm:ss'],
                ]
            )
            ->add(
                'sp_to',
                DatePickerType::class,
                [
                    'required' => false,
                    'label' => $this->translator->trans('to', [], 'Admin.Global'),
                    'attr' => ['placeholder' => 'YYYY-MM-DD HH:mm:ss'],
                ]
            )

from

->add(
                'sp_from',
                DatePickerType::class,
                [
                    'required' => false,
                    'label' => $this->translator->trans('Available from', [], 'Admin.Catalog.Feature'),
                    'attr' => ['placeholder' => 'YYYY-MM-DD'],
                ]
            )
            ->add(
                'sp_to',
                DatePickerType::class,
                [
                    'required' => false,
                    'label' => $this->translator->trans('to', [], 'Admin.Global'),
                    'attr' => ['placeholder' => 'YYYY-MM-DD'],
                ]
            )

Works fine but with style slitches. For me its not a problem. Now i can wait calm for official implementation in 1.7.7

time

@marionf do you think we will be able to add this option with the rework on the product page?

@colinegin yes, we talked about that with @matks

Hi , did you have some news ?

we are stuck with this issue on 1.7.6.5, impossible to add the hours

Hi,

Here is a dirty, but working solution for 1.7.6.5:

In src/prestashopBundle/Resources/view/admin/productPage/Forms/form_specific_price.html.twig
Replace {{ form_widget(form.sp_from) }} by html input, without datepickers class, and with type="datetime-local":
<div class="input-group"> <input value="{{ form.sp_from.vars.value }}" type="datetime-local" class="form-control" id="form_step2_specific_price_sp_from" name="form[step2][specific_price][sp_from]" /> <div class="input-group-append"></div> </div>

Do the same for {{ form_widget(form.sp_to) }}: dump it, copy the html input, remove datepicker class and set type="datetime-local".

See next post ==>

Now we have to format correctly the date for database, since html datetime-local don't format like datepicker.
Go Src/Adapter/Product/AdminProductWrapper.php

Change function processProductSpecificPrice:

$dateFrom = new \DateTime($specificPriceValues['sp_from']);
        $dateTo = new \DateTime($specificPriceValues['sp_to']);

        $floatParser = new FloatParser();

        // ---- data formatting ----
        $id_product_attribute = $specificPriceValues['sp_id_product_attribute'];
        $id_shop = $specificPriceValues['sp_id_shop'] ? $specificPriceValues['sp_id_shop'] : 0;
        $id_currency = $specificPriceValues['sp_id_currency'] ? $specificPriceValues['sp_id_currency'] : 0;
        $id_country = $specificPriceValues['sp_id_country'] ? $specificPriceValues['sp_id_country'] : 0;
        $id_group = $specificPriceValues['sp_id_group'] ? $specificPriceValues['sp_id_group'] : 0;
        $id_customer = !empty($specificPriceValues['sp_id_customer']['data']) ? $specificPriceValues['sp_id_customer']['data'][0] : 0;
        $price = isset($specificPriceValues['leave_bprice']) ? '-1' : $floatParser->fromString($specificPriceValues['sp_price']);
        $from_quantity = $specificPriceValues['sp_from_quantity'];
        $reduction = $floatParser->fromString($specificPriceValues['sp_reduction']);
        $reduction_tax = $specificPriceValues['sp_reduction_tax'];
        $reduction_type = !$reduction ? 'amount' : $specificPriceValues['sp_reduction_type'];
        $reduction_type = $reduction_type == '-' ? 'amount' : $reduction_type;
        $from = $dateFrom->format("Y-m-d H:i:s");
        if (!$from) {
            $from = '0000-00-00 00:00:00';
        }
        $to = $dateTo->format("Y-m-d H:i:s");
        if (!$to) {
            $to = '0000-00-00 00:00:00';
        }
        $isThisAnUpdate = (null !== $idSpecificPrice);

        // ---- validation ----
        if (($price == '-1') && ((float) $reduction == '0')) {
            $this->errors[] = $this->translator->trans('No reduction value has been submitted', array(), 'Admin.Catalog.Notification');
        } elseif ($to != '0000-00-00 00:00:00' && strtotime($to) < strtotime($from)) {
            $this->errors[] = $this->translator->trans('Invalid date range', array(), 'Admin.Catalog.Notification');
        } elseif ($reduction_type == 'percentage' && ((float) $reduction <= 0 || (float) $reduction > 100)) {
            $this->errors[] = $this->translator->trans('Submitted reduction value (0-100) is out-of-range', array(), 'Admin.Catalog.Notification');
        }
        $validationResult = $this->validateSpecificPrice(
            $id_product,
            $id_shop,
            $id_currency,
            $id_country,
            $id_group,
            $id_customer,
            $price,
            $from_quantity,
            $reduction,
            $reduction_type,
            $from,
            $to,
            $id_product_attribute,
            $isThisAnUpdate
        );

        if (false === $validationResult || count($this->errors)) {
            return $this->errors;
        }

        // ---- data modification ----
        if ($isThisAnUpdate) {
            $specificPrice = new SpecificPrice($idSpecificPrice);
        } else {
            $specificPrice = new SpecificPrice();
        }

        $specificPrice->id_product = (int) $id_product;
        $specificPrice->id_product_attribute = (int) $id_product_attribute;
        $specificPrice->id_shop = (int) $id_shop;
        $specificPrice->id_currency = (int) ($id_currency);
        $specificPrice->id_country = (int) ($id_country);
        $specificPrice->id_group = (int) ($id_group);
        $specificPrice->id_customer = (int) $id_customer;
        $specificPrice->price = (float) ($price);
        $specificPrice->from_quantity = (int) ($from_quantity);
        $specificPrice->reduction = (float) ($reduction_type == 'percentage' ? $reduction / 100 : $reduction);
        $specificPrice->reduction_tax = $reduction_tax;
        $specificPrice->reduction_type = $reduction_type;
        $specificPrice->from = $from;
        $specificPrice->to = $to;

        if ($isThisAnUpdate) {
            $dataSavingResult = $specificPrice->save();
        } else {
            $dataSavingResult = $specificPrice->add();
        }

        if (false === $dataSavingResult) {
            $this->errors[] = $this->translator->trans('An error occurred while updating the specific price.', array(), 'Admin.Catalog.Notification');
        }

        return $this->errors;

Go next and last post ==>

Now we have to reformat it for form displaying (when updating)
Go Src/PrestashopBundle/Controller/admin/SpecificPriceController.php

function updateAction:

$response = new JsonResponse();

        $formData = $request->get('form');

        $idProduct = isset($formData['id_product']) ? $formData['id_product'] : null;
        $formValues = $formData['modal'];
        $formValues['sp_from'] = $formData['step2']['specific_price']['sp_from'];
        $formValues['sp_to'] = $formData['step2']['specific_price']['sp_to'];

        /** @var AdminProductWrapper $adminProductWrapper */
        $adminProductWrapper = $this->get('prestashop.adapter.admin.wrapper.product');
        $errors = $adminProductWrapper->processProductSpecificPrice($idProduct, $formValues, $idSpecificPrice);

        if (!empty($errors)) {
            $response->setData(implode(', ', $errors));
            $response->setStatusCode(Response::HTTP_BAD_REQUEST);
        }

        return $response;

Function formatSpecificPriceToPrefillForm

if ($price->reduction_type === 'percentage') {
            $reduction = $price->reduction * 100;
        } else {
            $reduction = $price->reduction;
        }
        $formattedFormData = [
            'sp_update_id' => $id,
            'sp_id_shop' => $price->id_shop,
            'sp_id_currency' => $price->id_currency,
            'sp_id_country' => $price->id_country,
            'sp_id_group' => $price->id_group,
            'sp_id_customer' => null,
            'sp_id_product_attribute' => $price->id_product_attribute,
            'sp_from' => self::formatForHtmlDatetime($price->from),
            'sp_to' => self::formatForHtmlDatetime($price->to),
            'sp_from_quantity' => $price->from_quantity,
            'sp_price' => ($price->price !== '-1.000000') ? $price->price : '',
            'leave_bprice' => ($price->price === '-1.000000'),
            'sp_reduction' => $reduction,
            'sp_reduction_type' => $price->reduction_type,
            'sp_reduction_tax' => $price->reduction_tax,
        ];

        if ($price->id_customer !== '0') {
            $formattedFormData['sp_id_customer'] = ['data' => [$price->id_customer]];
        }
        $cleanedFormData = array_map(function ($item) {
            if (!$item) {
                return null;
            }

            return $item;
        }, $formattedFormData);

        return ['modal' => $cleanedFormData];

Now, Add this function:

/**
     * @param string $dateAsString
     *
     * @return JsonResponse|null If date is 0000-00-00 00:00:00, null is returned
     *
     * @throws \PrestaShopDatabaseExceptionCore if date is not valid
     */
    private static function formatForHtmlDatetime($dateAsString)
    {
        if ('0000-00-00 00:00:00' === $dateAsString) {
            return null;
        }

        try {
            $dateTime = new \DateTime($dateAsString);
        } catch (Exception $e) {
            throw new EntityDataInconsistencyException(sprintf('Found bad date for specific price: %s', $dateAsString));
        }

        return $dateTime->format('Y-m-d\TH:i:s');
    }

Hello,

it is planned to be added to at least the product page for the 178 version.
@prestascott we need to decide what should be the new design of the calendar with time (hours).

ping @MatShir FYI

Hello,

it is planned to be added to at least the product page for the 178 version.
@prestascott we need to decide what should be the new design of the calendar with time (hours).

@colinegin Are you sure ? Because this is a big change.

Allowing people to select minutes inside the BO page is only a small part of the overall needed work.

Because then we must also _update all the code logic that relies on it_. There is guarantee that the checkout logic that will be used to compute the cart total will work with minutes immediately, as today it only acknowledges hours.

Modifying the calendar is only a piece of the puzzle, and not solving the complete puzzle would be useless IMO. If you create a specific price that activate at 17pm27 but the code behind it only activates at 18pm00 it's useless.

To be discussed with @MatShir then, because this issue is part of the Pricing tab EPIC : https://github.com/PrestaShop/PrestaShop/issues/19673

This is quite an old topic, it's one of the big regressions of 17 vs 16, this feature has been removed from migrated pages, while it's available on legacy pages. So if I remember correctly (i might be wrong), the code is still able to manage minutes but the back office no longer allows it.

Hello @matks,

This is a big change but we also need this component for other pages such as Cart Rules. Merchants need to inform the date/hour/min of their discounts.

Here is my design proposition:
datepickerhourmin

Hello @matks,

This is a big change but we also need this component for other pages such as Cart Rules. Merchants need to inform the date/hour/min of their discounts.

Here is my design proposition:
datepickerhourmin

The design is something we can handle quite easily 馃槃 but it cannot be delivered alone.

If we want to allow merchants to select minutes:

  • merchants must be able to select minutes in the UI => this is your design
  • prestashop code must validate if the selected minutes are valid (= value between zero and sixty)
  • prestashop must STORE these selected minutes (= database change)
  • any prestashop logic that uses these informations must be updated* : this means the cart logic, the order logic, some display login on the FO, maybe more ... (we need to find all relevant code areas)

*If we dont do this step, the following scenario could happen:

  1. merchant chooses a specific price that starts at 17pm35 with 10% discount
  2. customer comes, orders at 17pm40 but the code that looks for eligible specific prices does not check minutes, only hours (because today it only allows hours) so he does not see the discount and customer pays full price

So I want to highlight that, because of "any prestashop logic that uses these informations must be updated" part, this is actually a big change 馃槃

Was this page helpful?
0 / 5 - 0 ratings