Skip to content

Developer Notes

Technical documentation for developers working with the Qoliber_StorePickup module.

Architecture Overview

The module follows Magento 2 best practices with a clean, extensible architecture:

Qoliber_StorePickup/
├── Api/                    # Service contracts and interfaces
├── Block/                  # Frontend blocks
├── Controller/             # Admin and frontend controllers
├── Model/                  # Business logic and repositories
├── Observer/               # Event observers
├── Plugin/                 # Interceptors
├── Service/                # Service layer
├── Ui/                     # UI components
└── view/                   # Frontend resources

Key Interfaces

Location Management

namespace Qoliber\StorePickup\Api;

interface LocationRepositoryInterface
{
    public function save(\Qoliber\StorePickup\Api\Data\LocationInterface $location);
    public function get(int $locationId);
    public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria);
    public function delete(\Qoliber\StorePickup\Api\Data\LocationInterface $location);
    public function deleteById(int $locationId);
}

Time Slot Management

namespace Qoliber\StorePickup\Api;

interface TimeSlotManagementInterface
{
    public function getAvailableSlots(int $locationId, string $date): array;
    public function reserveSlot(int $locationId, string $date, string $time): bool;
    public function releaseSlot(int $locationId, string $date, string $time): bool;
    public function getSlotCapacity(int $locationId, string $date, string $time): int;
}

Database Schema

Main Tables

qoliber_store_pickup_location

- location_id (int, PK)
- name (varchar)
- code (varchar, unique)
- status (smallint)
- phone (varchar)
- email (varchar)
- street (text)
- city (varchar)
- region (varchar)
- postcode (varchar)
- country_id (varchar)
- latitude (decimal)
- longitude (decimal)
- opening_hours (json)
- lead_time (int)
- max_orders_per_slot (int)
- created_at (timestamp)
- updated_at (timestamp)

qoliber_store_pickup_order

- entity_id (int, PK)
- order_id (int, FK)
- location_id (int, FK)
- pickup_date (date)
- pickup_time (time)
- status (varchar)
- marked_ready_by (varchar)
- marked_ready_at (timestamp)
- picked_up_by (varchar)
- picked_up_at (timestamp)

Event Observers

The module dispatches and observes several events:

Custom Events

// Dispatched when order is marked ready
qoliber_storepickup_order_ready
// Data: ['order' => $order, 'pickup_order' => $pickupOrder]

// Dispatched when order is picked up
qoliber_storepickup_order_picked_up
// Data: ['order' => $order, 'pickup_order' => $pickupOrder]

// Dispatched when time slot is reserved
qoliber_storepickup_slot_reserved
// Data: ['location_id' => $locationId, 'date' => $date, 'time' => $time]

Observed Events

  • sales_quote_address_collect_totals_before - Calculate pickup shipping
  • checkout_submit_all_after - Save pickup details to order
  • sales_order_place_after - Reserve time slot capacity

Plugin Integration Points

Checkout Integration

namespace Qoliber\StorePickup\Plugin\Checkout;

class ShippingInformationManagementPlugin
{
    public function beforeSaveAddressInformation(
        \Magento\Checkout\Model\ShippingInformationManagementInterface $subject,
        $cartId,
        \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
    ) {
        // Process pickup location selection
        // Save pickup date/time to quote
    }
}

JavaScript Components

Checkout Component

// view/frontend/web/js/view/checkout/shipping/store-selector.js
define([
    'uiComponent',
    'ko',
    'mage/url',
    'Magento_Checkout/js/model/quote'
], function (Component, ko, url, quote) {
    return Component.extend({
        defaults: {
            template: 'Qoliber_StorePickup/checkout/shipping/store-selector'
        },

        selectLocation: function(location) {
            // Handle location selection
        },

        loadTimeSlots: function() {
            // Load available time slots via AJAX
        }
    });
});

API Endpoints

REST API (planned)

GET    /V1/storepickup/locations
GET    /V1/storepickup/locations/:id
POST   /V1/storepickup/locations
PUT    /V1/storepickup/locations/:id
DELETE /V1/storepickup/locations/:id

GET    /V1/storepickup/timeslots/:locationId/:date
POST   /V1/storepickup/orders/:orderId/ready
POST   /V1/storepickup/orders/:orderId/pickup

GraphQL (Planned)

type Query {
    storePickupLocations(
        filter: StorePickupLocationFilterInput
        pageSize: Int
        currentPage: Int
    ): StorePickupLocations

    storePickupTimeSlots(
        locationId: Int!
        date: String!
    ): [TimeSlot]
}

type Mutation {
    setPickupLocation(
        cartId: String!
        locationId: Int!
        pickupDate: String
        pickupTime: String
    ): Cart
}

Extension Points

Adding Custom Fields to Locations

// via extension attributes in etc/extension_attributes.xml
<extension_attributes for="Qoliber\StorePickup\Api\Data\LocationInterface">
    <attribute code="custom_field" type="string"/>
</extension_attributes>

Customizing Time Slot Logic

// Implement custom time slot provider
class CustomTimeSlotProvider implements \Qoliber\StorePickup\Api\TimeSlotProviderInterface
{
    public function getSlots($locationId, $date)
    {
        // Custom logic for generating time slots
    }
}

// Register in di.xml
<preference for="Qoliber\StorePickup\Api\TimeSlotProviderInterface"
            type="Vendor\Module\Model\CustomTimeSlotProvider"/>

Email Template Variables

Available variables in email templates:

{{var order}}                  # Order object
{{var pickup_location}}         # Location object
{{var pickup_date}}            # Formatted pickup date
{{var pickup_time}}            # Formatted pickup time
{{var pickup_location.name}}   # Store name
{{var pickup_location.phone}}  # Store phone
{{var pickup_location.street}} # Store address

Performance Considerations

Indexing

  • Location data is flat for fast retrieval
  • Time slot availability uses dedicated index table
  • Quote/Order relation indexed for quick lookups

Caching

  • Location list cached with tag qoliber_storepickup_location
  • Time slots cached per day with TTL
  • Clear cache after location updates

Query Optimization

// Optimized location loading with specific fields
$this->locationCollection
    ->addFieldToSelect(['location_id', 'name', 'status'])
    ->addFieldToFilter('status', 1)
    ->setOrder('sort_order', 'ASC');

Testing

Unit Tests

vendor/bin/phpunit -c dev/tests/unit/phpunit.xml vendor/qoliber/store-pickup/Test/Unit

Integration Tests

vendor/bin/phpunit -c dev/tests/integration/phpunit.xml vendor/qoliber/store-pickup/Test/Integration

API Tests

# Test location endpoint
curl -X GET "https://magento.test/rest/V1/storepickup/locations" \
     -H "Authorization: Bearer {token}"

# Test time slots
curl -X GET "https://magento.test/rest/V1/storepickup/timeslots/1/2024-12-25" \
     -H "Authorization: Bearer {token}"

Common Customizations

Custom Pickup Fee Calculation

class CustomPickupPriceCalculator
{
    public function calculate($request)
    {
        // Custom logic based on location, time, items
        $location = $this->getSelectedLocation();
        $fee = $location->getBaseFee();

        if ($this->isRushHour($request->getPickupTime())) {
            $fee *= 1.5;
        }

        return $fee;
    }
}

Integration with ERP

class ErpSyncObserver implements \Magento\Framework\Event\ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $pickupOrder = $observer->getPickupOrder();

        // Send pickup details to ERP
        $this->erpApi->createPickupOrder([
            'order_id' => $pickupOrder->getOrderId(),
            'location_code' => $pickupOrder->getLocation()->getCode(),
            'pickup_date' => $pickupOrder->getPickupDate()
        ]);
    }
}

Troubleshooting

Debug Mode

Enable debug logging in etc/di.xml:

<type name="Qoliber\StorePickup\Model\Logger">
    <arguments>
        <argument name="debugEnabled" xsi:type="boolean">true</argument>
    </arguments>
</type>

Common Issues

Time slots not loading:

// Check JavaScript console for errors
// Verify AJAX endpoint: /storepickup/timeslot/available
// Check location has opening_hours JSON data

Location not saving:

// Check unique constraint on 'code' field
// Verify admin permissions for resource 'Qoliber_StorePickup::location_save'
// Check latitude/longitude format if map integration enabled

Support

For technical support and custom development: - Email: [email protected] - GitLab: Access available for partners - Documentation: https://docs.qoliber.com