Appearance
Extending
For developers
Add slot rules: cut-off, lead time, blackouts, capacity
PickupTimeSlotService::isAllowed() is the seam. It is used by both slot generation (what the customer sees) and isBookable() (what checkout accepts), so a rule added there cannot be bypassed by posting a hand-made time.
Decorate the service and override isAllowed():
php
namespace Acme\PickupRules;
use Kommandhub\ClickAndPickSW\Entity\PickupLocation\PickupLocationEntity;
use Kommandhub\ClickAndPickSW\PickupLocation\Availability\PickupTimeSlotService;
final class LeadTimeSlotService extends PickupTimeSlotService
{
protected function isAllowed(PickupLocationEntity $location, \DateTimeImmutable $when): bool
{
// Require two hours of preparation time.
return $when >= new \DateTimeImmutable('+2 hours');
}
}xml
<service id="Acme\PickupRules\LeadTimeSlotService"
decorates="Kommandhub\ClickAndPickSW\PickupLocation\Availability\PickupTimeSlotService">
<argument type="service" id="Kommandhub\ClickAndPickSW\PickupLocation\Availability\PickupLocationAvailabilityService"/>
</service>The constructor takes the availability service and an optional int $slotStepMinutes (default 30). Check the signatures in the version you extend; the plugin is pre-1.0.
$when is an absolute instant, so comparisons such as the one above are time-zone-safe. If a rule depends on the location's local day or hour, convert first: $when->setTimezone(new \DateTimeZone($location->getTimezone() ?? 'UTC')).
Change the slot length
Slots are 30 minutes because PickupTimeSlotService defaults $slotStepMinutes to DEFAULT_STEP_MINUTES. Override the argument in your own plugin's service configuration:
xml
<service id="Kommandhub\ClickAndPickSW\PickupLocation\Availability\PickupTimeSlotService">
<argument type="service" id="Kommandhub\ClickAndPickSW\PickupLocation\Availability\PickupLocationAvailabilityService"/>
<argument>15</argument>
</service>Checkout validation uses the same service, so the shorter slots are accepted.
React to pickup orders
Subscribe to the two events like any Symfony event, or use them as Flow Builder triggers:
php
use Kommandhub\ClickAndPickSW\Event\PickupOrderPlacedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
final class SendToWarehouse
{
#[AsEventListener(event: PickupOrderPlacedEvent::EVENT_NAME)]
public function __invoke(PickupOrderPlacedEvent $event): void
{
$location = $event->getPickupLocation(); // PickupLocationEntity
$record = $event->getOrderPickupLocation(); // time + comment
// …
}
}PickupOrderReadyEvent::EVENT_NAME (pickup.order.ready) carries the same getters. See Technical reference → Flow Builder.
Customise the emails
The two mail templates are ordinary Shopware templates (Settings → Email templates). Use the variables listed in Technical reference → Mail templates and format times with the location's zone:
twig
{{ pickupOrderLocation.pickupTime|format_datetime('medium', 'short', timezone=pickupLocation.timezone|default('UTC')) }}Read an order's pickup data
The record is autoloaded on every order:
php
$pickup = $order->getExtension('kmhPickupLocation');
$time = $pickup?->getPickupTime();
$location = $pickup?->getPickupLocation(); // null if the location was deletedTwig: order.extensions.kmhPickupLocation.pickupTime.
Reuse services instead of reaching into modules
| Service | Use it to |
|---|---|
PickupLocationSelectionResolver | Turn the current context into a resolved selection (loaded location + parsed time + comment). |
PickupLocationAvailabilityService | Ask whether a location is open at a time or on a date. |
PickupContextStorage | Read, write or clear the in-progress selection. |
OrderPickupLocationWriter | Write an order's pickup record. |
Reuse the flow contracts
PickupLocationAware and OrderPickupLocationAware (with their storers) let your own pickup-related events offer the same flow data and use the plugin's actions.
Template blocks
The checkout shipping-method override, the option templates and the pickup info card are wrapped in Twig blocks — the full list is in Technical reference → Templates and blocks. Extend them with sw_extends rather than copying templates. Shopware resolves the plugin's templates through the bundle chain, so a file at the same relative path in your plugin takes part in the inheritance. For example, to add a hint under the location dropdown, create Resources/views/storefront/component/shipping/custom/shipping-method.html.twig in your plugin:
twig
{% sw_extends '@KmhClickAndPickSW/storefront/component/shipping/custom/shipping-method.html.twig' %}
{% block component_shipping_method_label_kmh_self_pickup_location_field %}
{{ parent() }}
<small class="form-text">Orders are ready two hours after payment.</small>
{% endblock %}The Administration order tab is added through the core sw_order_detail_content_tabs_extension block.