PrestaShop / PrestaShop

PrestaShop is the universal open-source software platform to build your e-commerce solution.
https://www.prestashop-project.org/
Other
8.26k stars 4.82k forks source link

[Idea] Change specific price date ranges to DateTime #11329

Closed bmwjoeytsang closed 1 year ago

bmwjoeytsang commented 6 years ago

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

khouloudbelguith commented 6 years ago

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!

marionf commented 6 years ago

Yes, I think it will be nice to add it

@colinegin wdyt ?

lvancrayelynghe commented 6 years ago

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)

colinegin commented 6 years ago

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.

colinegin commented 6 years ago

See PR : https://github.com/PrestaShop/PrestaShop/pull/8571

khouloudbelguith commented 5 years ago

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!

ghost commented 5 years ago

/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'],
                ]
            )
ghost commented 5 years ago

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

colinegin commented 5 years ago

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

marionf commented 5 years ago

@colinegin yes, we talked about that with @matks

Dreanad commented 4 years ago

Hi , did you have some news ?

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

Florkin commented 4 years ago

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-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 ==>

Florkin commented 4 years ago

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 ==>

Florkin commented 4 years ago

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');
    }
colinegin commented 4 years ago

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

matks commented 4 years ago

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.

colinegin commented 4 years ago

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.

prestascott commented 4 years ago

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

matks commented 4 years ago

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 😄

kmorgen commented 3 years ago

Unfortunately this problem never seem to have been fixed.

In Discount/Voucher section it is possible to set actual time - but never seem to have been fixed for Specific Prices in PS1.7.

Is there a quickfix for this, as it is very annoying during one-day offers like Black Friday, not being able to adjust this?!

ghost commented 3 years ago

UP!

MatShir commented 3 years ago

No fix currently for this issue, but you can a cart rule with product constraints and a time constraint. But I know it is not ideal. 😞

az-vm commented 2 years ago

Hello!

As they still haven't decided to put the time to work in the date-picker of discounts.

Wouldn't it be possible to take 1 day off the code that shows the end date of the promotion?

This code displays the end date of the promotion, but due to the time issue the promotion ends before the end of the day, so we have to add a day on the backoffice side. And the idea in this code was to remove the extra day to hit the date right, you know?

{l s='End promo: '}{$product->specificPrice.to|date_format:'%d-%m-%Y' . "-1 days""}

I await your comments.

Thanks!

HartLarsson commented 1 year ago

1.7.8.8 and the problem is not fixed yet.