lunar icon indicating copy to clipboard operation
lunar copied to clipboard

Support discounts specifying fields in the discount UI

Open ryanmitchell opened this issue 2 years ago • 3 comments

Currently custom discounts cant specify fields the filament UI.

This PR attempts to fix that by allowing discounts to specify 3 'magic' methods:

lunarPanelSchema(): array The Filament schema for the fields to be displayed when the discount type is specified

lunarPanelOnFill(array $data): array To allow the discount to modify the data associated with the discount before its rendered (only called if the discount type is selected)

lunarPanelOnSave(array $data): array To allow the discount to modify the data associated with the discount before its saved (only called if the discount type is selected)

Here is an example of how it is used, in a Amount off (Retail price) discount that we use in one of our systems:

    public function lunarPanelSchema(): array
    {
        $currencies = Currency::get();

        $currencyInputs = [];

        foreach ($currencies as $currency) {
            $currencyInputs[] = Forms\Components\TextInput::make(
                'data.retail.fixed_values.'.$currency->code
            )
            ->label($currency->name)
            ->afterStateHydrated(function (Forms\Components\TextInput $component, $state) use ($currencies) {
                $currencyCode = last(explode('.', $component->getStatePath()));
                $currency = $currencies->first(
                    fn ($currency) => $currency->code == $currencyCode
                );

                if ($currency) {
                    $component->state($state / $currency->factor);
                }
            });
        }

        return [
            Forms\Components\Toggle::make('data.retail.fixed_value')->live(),
            Forms\Components\TextInput::make('data.retail.percentage')->visible(
                fn (Forms\Get $get) => ! $get('data.retail.fixed_value')
            )->numeric(),
            Forms\Components\Group::make(
                $currencyInputs
            )->visible(
                fn (Forms\Get $get) => (bool) $get('data.retail.fixed_value')
            )->columns(3),
        ];
    }

    public function lunarPanelOnFill(array $data): array
    {
        if (! Arr::get($data, 'data.retail', false)) {
            $dataValues = $data['data'] ?? [];

            $dataValues['retail'] = [];

            if (isset($dataValues['fixed_value'])) {
                $dataValues['retail']['fixed_value'] = $dataValues['fixed_value'];
                unset($dataValues['fixed_value']);
            }

            if (isset($dataValues['fixed_values'])) {
                $dataValues['retail']['fixed_values'] = $dataValues['fixed_values'];
                unset($dataValues['fixed_values']);
            }

            if (isset($dataValues['percentage'])) {
                $dataValues['retail']['percentage'] = $dataValues['percentage'];
                unset($dataValues['percentage']);
            }

            $data['data'] = $dataValues;
        }

        return $data;
    }

    public function lunarPanelOnSave(array $data): array
    {
        if ($retail = Arr::get($data, 'data.retail', false)) {
            $data['data'] = array_merge($data['data'], $retail);
            unset($data['data']['retail']);

            $currencies = Currency::enabled()->get();
            $fixedPrices = $data['data']['fixed_values'] ?? [];
            foreach ($fixedPrices as $currencyCode => $fixedPrice) {
                $currency = $currencies->first(
                    fn ($currency) => $currency->code == $currencyCode
                );

                if (! $currency) {
                    continue;
                }
                $data['data']['fixed_values'][$currencyCode] = (int) round($fixedPrice * $currency->factor);
            }
        }

        return $data;
    }

As you can see we are using some of the same data values as the standard "Amount off" discount, but we have to prefix them in the schema to avoid collisions on the filament side... then on fill/save we add/undo those prefixes so they dont get saved to the database. This is just a simple example to show how they can used and why they are necessary.

The benefit of this approach is that the discount is just registered once, making the DX experience simpler.

        Discounts::addType(
            DiscountTypes\AmountOffRetail::class
        );

ryanmitchell avatar Mar 18 '24 08:03 ryanmitchell

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
lunar-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 14, 2024 8:24am

vercel[bot] avatar Mar 18 '24 08:03 vercel[bot]

Thanks @ryanmitchell Going to try and get my head into and take a look at this soon :) @glennjacobs did you have any opinions in the meantime?

alecritson avatar Mar 27 '24 13:03 alecritson

Any more thoughts on this one? Its the only thing holding us back from rolling 1.x out, so it would be great to find a solution.

ryanmitchell avatar May 31 '24 07:05 ryanmitchell

@ryanmitchell I think this approach is fine. Here are my thoughts...

The new methods don't implement an interface but I think they should. The core provides the DiscountTypeInterface and I feel like the admin package should provide its own interface for these three new methods.

And then finally we could do with updating the documentation to cover this.

glennjacobs avatar Jul 05 '24 15:07 glennjacobs

@glennjacobs thanks for reviewing and your feedback. I've pushed up the changes requested.

ryanmitchell avatar Jul 09 '24 03:07 ryanmitchell

Just checking in on this - it would be great to get reviewed again to make the next alpha if possible.

ryanmitchell avatar Jul 18 '24 17:07 ryanmitchell