166 lines
5.6 KiB
PHP
166 lines
5.6 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace Modules\Core\Shipping\Filament\Pages;
|
||
|
|
|
||
|
|
use Filament\Forms\Components\Select;
|
||
|
|
use Filament\Forms\Form;
|
||
|
|
use Filament\Forms\Get;
|
||
|
|
use Filament\Tables\Columns\TextColumn;
|
||
|
|
use Filament\Tables\Table;
|
||
|
|
use Lunar\Shipping\Facades\Shipping;
|
||
|
|
use Lunar\Shipping\Models\ShippingMethod;
|
||
|
|
use Lunar\Shipping\Models\ShippingRate;
|
||
|
|
use Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages\ManageShippingRates as BaseManageShippingRates;
|
||
|
|
use Modules\Core\Shipping\Contracts\SupportsLivePricing;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Bound in place of the vendor ManageShippingRates page via the container
|
||
|
|
* (see ShippingServiceProvider), since that page has no extension hook of
|
||
|
|
* its own. Every reference to the vendor class name — routes, sub-nav,
|
||
|
|
* ShippingZoneResource::getPages() — is untouched; the container simply
|
||
|
|
* hands back this subclass whenever the vendor class is resolved.
|
||
|
|
*
|
||
|
|
* Adds a per-rate "Pricing" toggle (live API vs. fixed price) for methods
|
||
|
|
* whose driver supports live pricing (see SupportsLivePricing). Rates on
|
||
|
|
* methods without live pricing behave exactly as the vendor page always did
|
||
|
|
* — no toggle shown, price fields always required, vendor save logic used
|
||
|
|
* as-is.
|
||
|
|
*/
|
||
|
|
class ManageShippingRates extends BaseManageShippingRates
|
||
|
|
{
|
||
|
|
public function form(Form $form): Form
|
||
|
|
{
|
||
|
|
$form = parent::form($form);
|
||
|
|
|
||
|
|
return $form->schema(
|
||
|
|
$this->insertPricingModeFieldAfterShippingMethod(
|
||
|
|
$this->hidePriceFieldsWhenLive($form->getComponents())
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Insert the "Pricing" field immediately after "shipping_method_id" so
|
||
|
|
* it reads as a pair, rather than appending it elsewhere in the form.
|
||
|
|
*/
|
||
|
|
private function insertPricingModeFieldAfterShippingMethod(array $components): array
|
||
|
|
{
|
||
|
|
$result = [];
|
||
|
|
|
||
|
|
foreach ($components as $component) {
|
||
|
|
$result[] = $component;
|
||
|
|
|
||
|
|
if (method_exists($component, 'getName') && $component->getName() === 'shipping_method_id') {
|
||
|
|
$result[] = $this->pricingModeField();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function pricingModeField(): Select
|
||
|
|
{
|
||
|
|
return Select::make('pricing_mode')
|
||
|
|
->label('Pricing')
|
||
|
|
->options([
|
||
|
|
'live' => 'Use live API pricing',
|
||
|
|
'fixed' => 'Use a fixed price',
|
||
|
|
])
|
||
|
|
->default('live')
|
||
|
|
->live()
|
||
|
|
->visible(fn (Get $get) => static::methodHasLivePricing($get('shipping_method_id')))
|
||
|
|
->columnSpan(2);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Hide the vendor's price / price-break fields whenever this rate is
|
||
|
|
* set to live pricing — they'd otherwise be dead configuration,
|
||
|
|
* silently ignored by the driver. Only the base "price" field was ever
|
||
|
|
* required by the vendor form; the "prices" repeater (price breaks) is
|
||
|
|
* always optional, so its required() state is left untouched.
|
||
|
|
*/
|
||
|
|
private function hidePriceFieldsWhenLive(array $components): array
|
||
|
|
{
|
||
|
|
$isFixedOrNotLiveCapable = fn (Get $get) => ! static::methodHasLivePricing($get('shipping_method_id'))
|
||
|
|
|| $get('pricing_mode') === 'fixed';
|
||
|
|
|
||
|
|
foreach ($components as $component) {
|
||
|
|
if (! method_exists($component, 'getName')) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($component->getName() === 'price') {
|
||
|
|
$component->visible($isFixedOrNotLiveCapable)
|
||
|
|
->required($isFixedOrNotLiveCapable)
|
||
|
|
->dehydrated(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($component->getName() === 'prices') {
|
||
|
|
$component->visible($isFixedOrNotLiveCapable)
|
||
|
|
->dehydrated(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $components;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function table(Table $table): Table
|
||
|
|
{
|
||
|
|
$table = parent::table($table);
|
||
|
|
|
||
|
|
return $table->columns(
|
||
|
|
array_map(function ($column) {
|
||
|
|
if (method_exists($column, 'getName') && $column->getName() === 'basePrices.0') {
|
||
|
|
return TextColumn::make('basePrices.0')
|
||
|
|
->label(__('lunarpanel.shipping::relationmanagers.shipping_rates.table.price.label'))
|
||
|
|
->formatStateUsing(function ($state, ShippingRate $record) {
|
||
|
|
if (static::methodHasLivePricing($record->shipping_method_id) && $record->pricing_mode === 'live') {
|
||
|
|
return 'Live API pricing';
|
||
|
|
}
|
||
|
|
|
||
|
|
return $state?->price->formatted;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return $column;
|
||
|
|
}, $table->getColumns())
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected static function saveShippingRate(?ShippingRate $shippingRate = null, array $data = []): void
|
||
|
|
{
|
||
|
|
$isLive = static::methodHasLivePricing($data['shipping_method_id'] ?? $shippingRate?->shipping_method_id)
|
||
|
|
&& ($data['pricing_mode'] ?? 'live') === 'live';
|
||
|
|
|
||
|
|
$shippingRate->pricing_mode = $isLive ? 'live' : 'fixed';
|
||
|
|
$shippingRate->save();
|
||
|
|
|
||
|
|
if ($isLive) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
parent::saveShippingRate($shippingRate, $data);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected static function methodHasLivePricing(ShippingMethod|int|string|null $method): bool
|
||
|
|
{
|
||
|
|
if (blank($method)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (! $method instanceof ShippingMethod) {
|
||
|
|
$method = ShippingMethod::find($method);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (! $method) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
return Shipping::driver($method->driver) instanceof SupportsLivePricing;
|
||
|
|
} catch (\InvalidArgumentException) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|