How to Build a Custom Delivery Date Selection Module in Magento 2

In this post I’ll walk you through comment build a custom “Delivery Date Selection” module for Magento 2. We’ll cover architecture, creating attributs personnalisés and DB tables, integnote a paiement UI composant, handling constraints (holidays, lead times, time slots), UX conseils for both the vitrine and the admin backoffice, and comment keep it compatible with stock management and other Magefine modules. I’ll keep things practical with étape-by-étape code exemples so you can copy, adapt and ship.

Why build a Delivery Date Selection module?

Delivery date selection improves conversion and reduces support enquiries by letting clients choose a convenient delivery day and time. Most commerçants need entreprise rules: exclude holidays, enforce preparation time, allow only certain time windows and keep the module compatible with inventaire/fulfillment extensions. Building your own module gives full control and tight integration with Magento 2 paiement and admin.

High-level architecture

At a glance, the module is composed of:

  • Database: custom tables to store configurable time slots, holidays and commande delivery info.
  • Config: system configuration to control lead times, available days, and time slot behavior.
  • Checkout front-end: a UI composant (Knockout JS) that integrates into the Magento 2 paiement and collects selected date and slot.
  • Back-end: admin UI to manage holidays, time slots and commande delivery meta; observateurs to persist data on commande placement.
  • Integration points: observateurs/plugins to keep compatibility with inventaire, shipping and other Magefine extensions.

Module skeleton

We’ll call the module Magefine_DeliveryDate. Create the basic module fichiers:

app/code/Magefine/DeliveryDate/registration.php
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Magefine_DeliveryDate',
    __DIR__
);

app/code/Magefine/DeliveryDate/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magefine_DeliveryDate" setup_version="1.0.0" />
</config>

Database: schema and sample table structure

You need tables for:

  • delivery_date_commande — store the selected date and slot per commande
  • delivery_date_slots — available time slots and rules
  • delivery_date_holidays — blocked dates

Example declarative schema (db_schema.xml):

app/code/Magefine/DeliveryDate/etc/db_schema.xml
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="magefine_delivery_slots" resource="default" engine="innodb" comment="Magefine delivery slots">
        <colonne xsi:type="int" name="slot_id" padding="10" unsigned="true" nullable="false" identity="true" />
        <colonne xsi:type="varchar" name="label" length="255" nullable="false" />
        <colonne xsi:type="time" name="from" nullable="false" />
        <colonne xsi:type="time" name="to" nullable="false" />
        <colonne xsi:type="tinyint" name="is_active" nullable="false" default="1" />
        <colonne xsi:type="int" name="max_commandes" nullable="true" /> 
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <colonne name="slot_id" />
        </constraint>
    </table>

    <table name="magefine_delivery_holidays" resource="default" engine="innodb" comment="Magefine delivery holidays">
        <colonne xsi:type="int" name="holiday_id" nullable="false" identity="true" unsigned="true" />
        <colonne xsi:type="date" name="date" nullable="false" />
        <colonne xsi:type="varchar" name="label" length="255" nullable="true" />
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <colonne name="holiday_id" />
        </constraint>
    </table>

    <table name="magefine_commande_delivery" resource="default" engine="innodb" comment="Magefine commande delivery info">
        <colonne xsi:type="int" name="entity_id" nullable="false" identity="true" unsigned="true" />
        <colonne xsi:type="int" name="commande_id" nullable="false" unsigned="true" />
        <colonne xsi:type="date" name="delivery_date" nullable="true" />
        <colonne xsi:type="varchar" name="time_slot_label" length="255" nullable="true" />
        <colonne xsi:type="int" name="slot_id" nullable="true" unsigned="true" />
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <colonne name="entity_id" />
        </constraint>
    </table>
</schema>

System configuration

Use system.xml to expose settings tel que lead time (in days), allowed weekdays, default time slots per day, and whether to show in shipping étape or avis étape.

app/code/Magefine/DeliveryDate/etc/adminhtml/system.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_fichier.xsd">
  <system>
    <section id="delivery_date" translate="label" triOrder="200" showInDefault="1" showInWebsite="1" showInStore="1">
      <label>Delivery Date Settings</label>
      <group id="general" translate="label" triOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
        <label>General</label>
        <champ id="lead_time" translate="label" type="text" triOrder="10" showInDefault="1">
          <label>Preparation time (days)</label>
          <comment>Minimum number of days between commande and delivery</comment>
        </champ>
        <champ id="allowed_weekdays" translate="label" type="multiselect" triOrder="20" showInDefault="1">
          <label>Allowed weekdays</label>
          <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
        </champ>
      </group>
    </section>
  </system>
</config>

Creating a attribut personnalisé or saving delivery data to commande

We have two common approchees: add a custom commande attribute (EAV or extension attributes) or create your own commande delivery table linking to commande_id. Using a separate table (magefine_commande_delivery) keeps commande schema simple and is easier to maintain. But sometimes you want to show delivery info in commande grid and export; in that case use commande extension attributes or add colonnes via extension attributes and plugins.

Example: observateur to save delivery selection when commande is placed. We’ll capture the selected valeurs from the quote and persist them on commande success.

app/code/Magefine/DeliveryDate/etc/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="paiement_submit_all_after">
        <observateur name="magefine_delivery_save" instance="Magefine\DeliveryDate\Observer\SaveDeliveryToOrder" />
    </event>
</config>

app/code/Magefine/DeliveryDate/Observer/SaveDeliveryToOrder.php
<?php
namespace Magefine\DeliveryDate\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class SaveDeliveryToOrder implements ObserverInterface
{
    protected $deliveryFactory;

    public fonction __construct(\Magefine\DeliveryDate\Model\DeliveryFactory $deliveryFactory)
    {
        $this->deliveryFactory = $deliveryFactory;
    }

    public fonction execute(Observer $observateur)
    {
        $commande = $observateur->getEvent()->getOrder();
        $quote = $observateur->getEvent()->getQuote();

        // Retrieve data stored on quote (we’ll set it from the paiement JS)
        $deliveryDate = $quote->getData('magefine_delivery_date');
        $timeSlotId = $quote->getData('magefine_delivery_slot_id');
        $timeSlotLabel = $quote->getData('magefine_delivery_slot_label');

        if ($deliveryDate) {
            $model = $this->deliveryFactory->create();
            $model->setData([
                'commande_id' => $commande->getEntityId(),
                'delivery_date' => $deliveryDate,
                'slot_id' => $timeSlotId,
                'time_slot_label' => $timeSlotLabel
            ]);
            $model->save();
        }
    }
}

Checkout UI: où put the composant

You have two common options:

  • Integrate into the shipping étape (shipping-address) so client selects date before payment.
  • Place it in commande avis only (paiement_index_index layout) — less ideal for shipping-dependent rules.

Example: adding a custom UI composant to the shipping étape via layout and requirejs mixin or a custom paiement layout update.

app/code/Magefine/DeliveryDate/view/frontend/layout/paiement_index_index.xml
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="paiement.container">
            <classe de bloc="Magento\Checkout\Block\Onepage" name="magefine.delivery.paiement" template="Magefine_DeliveryDate::paiement/delivery.phtml" before="-"/>
        </referenceContainer>
    </body>
</page>

But probably the best approche is to register a UI composant as part of the shipping étape’s layout JS configuration. Use mixins to extend Magento_Checkout/js/view/shipping or create your own composant and insert it into paiementSteps.

Front-end JS composant (simplified)

Create a UI composant with KnockoutJS to show a date picker and time slots. Use Magento’s clientData/quote to save valeurs to the quote via an AJAX call to a contrôleur that adds them as quote attributes.

app/code/Magefine/DeliveryDate/view/frontend/web/js/view/delivery-date.js
define([
    'uiComponent',
    'ko',
    'mage/storage',
    'Magento_Checkout/js/model/quote',
    'Magento_Ui/js/modal/alert'
], fonction (Component, ko, storage, quote, alert) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Magefine_DeliveryDate/delivery-date'
        },
        selectedDate: ko.observable(null),
        selectedSlot: ko.observable(null),
        slots: ko.observableArray([]),

        initialize: fonction () {
            this._super();
            // Load available slots via AJAX
            storage.get('deliverydate/ajax/slots')
                .done(fonction (response) {
                    this.slots(response.slots);
                }.bind(this));
        },

        save: fonction () {
            var payload = {
                date: this.selectedDate(),
                slot_id: this.selectedSlot() ? this.selectedSlot().id : null,
                slot_label: this.selectedSlot() ? this.selectedSlot().label : null
            };

            storage.post('deliverydate/ajax/saveQuote', JSON.chaîneify(payload))
                .done(fonction () {
                    // update quote data or show confirmation
                })
                .fail(fonction (){
                    alert({contenu: 'Unable to save delivery date.'});
                });
        }
    });
});

KO template (simple)

app/code/Magefine/DeliveryDate/view/frontend/web/templates/delivery-date.html
<div class="magefine-delivery-date">
    <label>Choose delivery date</label>
    <input data-bind="valeur: selectedDate, event: {change: save}" type="date" />

    <div data-bind="foreach: slots">
        <label>
            <input type="radio" name="delivery_slot" data-bind="valeur: $data, checked: $parent.selectedSlot, click: $parent.save" />
            <span data-bind="text: label"></span>
        </label>
    </div>
</div>

Server-side contrôleur to accept quote changes

app/code/Magefine/DeliveryDate/Controller/Ajax/SaveQuote.php
<?php
namespace Magefine\DeliveryDate\Controller\Ajax;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Quote\Model\QuoteRepository;
use Magento\Checkout\Model\Session as CheckoutSession;

class SaveQuote extends Action
{
    protected $paiementSession;
    protected $quoteRepository;

    public fonction __construct(Context $context, CheckoutSession $paiementSession, QuoteRepository $quoteRepository)
    {
        parent::__construct($context);
        $this->paiementSession = $paiementSession;
        $this->quoteRepository = $quoteRepository;
    }

    public fonction execute()
    {
        $data = json_decode($this->getRequest()->getContent(), true);
        $quote = $this->paiementSession->getQuote();
        if (!empty($data['date'])) {
            $quote->setData('magefine_delivery_date', $data['date']);
            $quote->setData('magefine_delivery_slot_id', $data['slot_id']);
            $quote->setData('magefine_delivery_slot_label', $data['slot_label']);
            $this->quoteRepository->save($quote);
        }
        $this->getResponse()->setBody(json_encode(['success' => true]));
    }
}

Handling constraints: holidays, lead time, and time slots

C'est the core logic. On the server side provide an endpoint that returns valid dates and slots taking account of:

  • Configured lead time: now + lead_time days devrait être earliest selectable date.
  • Holidays table: excluded dates.
  • Allowed weekdays: commerçant might disable specific weekdays.
  • Slot capacity: optional limit of commandes per slot per day.
  • Time windows: compute available slots per selected date.

Example: slot provider logic (simplified):

app/code/Magefine/DeliveryDate/Model/AvailabilityProvider.php
<?php
namespace Magefine\DeliveryDate\Model;

use Magento\Framework\Stdlib\DateTime\DateTime;

class AvailabilityProvider
{
    protected $holidayCollectionFactory;
    protected $slotCollectionFactory;
    protected $scopeConfig;
    protected $date;

    public fonction __construct(
        \Magefine\DeliveryDate\Model\ResourceModel\Holiday\CollectionFactory $holidayCollectionFactory,
        \Magefine\DeliveryDate\Model\ResourceModel\Slot\CollectionFactory $slotCollectionFactory,
        \Magento\Store\Model\ScopeInterface $scopeConfig,
        DateTime $date
    ) {
        $this->holidayCollectionFactory = $holidayCollectionFactory;
        $this->slotCollectionFactory = $slotCollectionFactory;
        $this->scopeConfig = $scopeConfig;
        $this->date = $date;
    }

    public fonction getAvailableDates($rangeDays = 30)
    {
        // compute from today + lead_time
        $leadTime = (int)$this->scopeConfig->getValue('delivery_date/general/lead_time');
        $start = strtotime('+' . $leadTime . ' days');
        $holidays = $this->holidayCollectionFactory->create()->getColumnValues('date');

        $available = [];
        for ($i = 0; $i < $rangeDays; $i++) {
            $ts = strtotime('+' . ($i + $leadTime) . ' days');
            $date = date('Y-m-d', $ts);
            $weekday = date('N', $ts); // 1 (Mon) - 7 (Sun)

            // check weekday allowed - exemple: store config contains allowed weekdays as tableau
            $allowedWeekdays = explode(',', $this->scopeConfig->getValue('delivery_date/general/allowed_weekdays') ?: '1,2,3,4,5');
            if (!in_tableau($weekday, $allowedWeekdays)) continue;

            if (in_tableau($date, $holidays)) continue;

            // optionally check slot capacity
            $available[] = $date;
        }

        return $available;
    }
}

Slot capacity and concurrency

When your store is busy you may want to limit how many commandes peut être scheduled per slot per day. Typical design:

  1. Count existing commandes for date+slot (from magefine_commande_delivery table).
  2. Compare to slot.max_commandes and disable if full.
  3. Use transactions or optimistic locking to avoid race conditions at high concurrency — e.g. reserve on paiement submit and confirm on commande placement.

Example SQL-ish pseudo logic:

SELECT COUNT(*) FROM magefine_commande_delivery WHERE delivery_date = :date AND slot_id = :slot_id
IF count >= slot.max_commandes THEN slot not available

Admin backoffice: manage holidays and time slots

Create grille d'administrations for slots and holidays using standard Magento UI composants (ui_composant grids and forms). This provides UX for administrators to add/remove holidays and manage slots. Also expose system settings in Stores > Configuration for global settings.

Example admin menu item and ACL to access the grid:

app/code/Magefine/DeliveryDate/etc/adminhtml/menu.xml
<menu xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Menu/etc/menu.xsd">
    <add id="Magefine_DeliveryDate::delivery" title="Delivery Date" module="Magefine_DeliveryDate" triOrder="90" parent="Magento_Sales::sales" action="deliverydate/slot/index" resource="Magefine_DeliveryDate::delivery"/>
</menu>

Displaying delivery info in admin commande view

You’ll want to add a panel in the admin commande view showing the selected delivery date & slot. Use layout xml to add a block to the commande view or use an observateur to append data to commande view blocks. Also provide the ability to change delivery date from admin (respecting constraints).

Integration with inventaire, shipping and other Magefine modules

Compatibility is clé. Voici recommended practices to keep it harmonious:

  • Do not rewrite core classes. Use plugins or observateurs.
  • Use extension attributes or a dedicated table rather than adding fragile changes to sales_commande table. That reduces conflicts with inventaire or other extensions that alter commandes.
  • Listen to events rather than hard-coding flows. Par exemple, observe paiement_submit_all_after au lieu de forcing a custom paiement pipeline.
  • Si vous need to reserve inventaire for a slot, integrate with MSI (Multi Source Inventory) APIs or your stock module: create a reservation entry with source selection or notify the fulfillment module about the scheduled expédition.
  • Provide extension points: preferences, interfaces and events so other Magefine extensions can read/write delivery data. Par exemple, discorrectif a custom event magefine_delivery_reserve to let the stock/reservation module act.

Example: discorrectif event for reservation:

// In SaveDeliveryToOrder::execute after saving delivery info
$this->_eventManager->discorrectif('magefine_delivery_reserve', ['commande' => $commande, 'delivery' => $model]);

UX: front-end and admin bonnes pratiques

UX matters. Voici practical conseils to reduce friction:

  • Keep the date picker simple: show earliest selectable date and disabled blocked dates (holidays, weekends if disabled).
  • Show time slot capacity and “few slots left” hints if capacity est activé — creates urgency and transparency.
  • Validate server-side and client-side. Never rely only on client validation for blocked dates or capacity.
  • Keep paiement flow minimal: if you need an extra click, make it clear why (e.g. choose delivery date)
  • In admin, provide bulk actions for shifting many commandes to another delivery date (helpful in case of closures).
  • Expose delivery data on commande e-mails and PDF factures (so entrepôt & client have the info).

Testing stratégie

Test the module thoroughly with:

  • Unit tests for availability provider and entreprise rules.
  • Integration tests for quote-to-commande flow.
  • Manual QA of paiement flow with mulconseille payment/méthodes de livraison and with and without logged in clients.
  • Concurrency tests to simulate many clients booking the same slot.

Performance considerations

Computing available dates for many clients concurrently doit être efficient. Strategies:

  • Cache static results (e.g. list of holidays, slot definitions) and only refresh when admin changes them.
  • Keep DB indexes on delivery_date, slot_id and commande_id for fast counts.
  • Limit server-side computation to necessary ranges (e.g. next 30 days).

Example: show delivery date on commande e-mails

Add a small template variable populated from the commande delivery table or extension attribute. Use a plugin on \Magento\Sales\Model\Order\Email\SenderBuilder or attach a variable via e-mail templates.

Admin UX: change delivery date from commande view

Provide an admin form in Sales > Orders to change the delivery date. When changing, re-run constraint checks (holidays, slot capacity) and, if needed, notify fulfillment. Consider logging changes and optionally notifying the client.

Handling special cases

Common edge cases and comment handle them:

  • Order edited after placement: keep original delivery date but mark it changed, or charge a rescheduling fee — implement as entreprise logic.
  • Shipping méthode affects availability: e.g. express courier delivers on different days. Use méthode de livraison code when computing availability.
  • Multi-shipping paiement: you may need per-address delivery dates. Save selections per quote address.

Compatibility with Magefine extensions

Si vous or your client uses other Magefine modules (for exemple advanced shipping, paiement improvements or hosting integrations) follow these guidelines:

  • Check for conflicts in observateurs and events. Avoid listening to generic events that many extensions use; use your own event names for extension points.
  • If other Magefine modules offer shipping or reservation fonctionnalités, provide a small integration doc or classe helper so they can read the delivery selection via API (e.g. DeliveryRepositoryInterface::getByOrderId($commandeId)).
  • Test the module with Magefine’s common extensions (e.g. advanced shipping, payment modules) and with the hosting environment to ensure caching layers don’t block dynamic date calculation. Cache dynamic endpoints via AJAX and bypass cache pleine page for per-client available slots calls.

Security and privacy

Delivery dates are not sensitive personal data, but keep the usual good practices:

  • Sanitize any admin inputs (dates, labels).
  • Protect admin endpoints with ACL.
  • Only expose delivery info to authorized utilisateurs (commande view, client area).

Extensibility: comment let other modules hook in

Expose interfaces and events so other modules can extend behavior without editing your code. Example services to provide:

  • DeliveryRepositoryInterface: read/write delivery data for a given commande.
  • AvailabilityProviderInterface: given a date range and context (méthode de livraison, address), return available slots.
  • Events: magefine_delivery_before_save_quote, magefine_delivery_after_save_quote, magefine_delivery_reserve

Real-world conseils from shipping teams

  • Keep a default buffer between commande and delivery to account for picking/packing.
  • Consider setting different lead times per product type or entrepôt — integrate with MSI sources or attribut produits.
  • Allow cut-off time for same-day delivery if you support it (e.g. if now < 2pm allow same-day otherwise start tomorligne).

Step-by-étape recap to build the module

  1. Create module skeleton and register it in Magento.
  2. Add db_schema.xml for slots, holidays and commande delivery table.
  3. Add system.xml to expose lead time and allowed weekdays in admin config.
  4. Create admin UI grids for slots and holidays (ui_composant).
  5. Create front-end Knockout UI composant to pick date and slot, and a contrôleur to persist to quote.
  6. Create observateur to persist quote delivery data into your commande delivery table on commande placement.
  7. Implement AvailabilityProvider that respects lead time, holidays and slot capacity.
  8. Add commande view block in admin to display/change delivery date and discorrectif reservation events.
  9. Test thoroughly and add unit/test d'intégrations; ensure compatibility with other Magefine modules and MSI.

Example: DeliveryRepositoryInterface and a simple implémentation

app/code/Magefine/DeliveryDate/Api/DeliveryRepositoryInterface.php
<?php
namespace Magefine\DeliveryDate\Api;

interface DeliveryRepositoryInterface
{
    public fonction getByOrderId($commandeId);
    public fonction save(\Magefine\DeliveryDate\Model\Delivery $delivery);
}

app/code/Magefine/DeliveryDate/Model/DeliveryRepository.php
<?php
namespace Magefine\DeliveryDate\Model;

use Magefine\DeliveryDate\Api\DeliveryRepositoryInterface;

class DeliveryRepository implements DeliveryRepositoryInterface
{
    protected $resource;
    protected $factory;
    protected $collectionFactory;

    public fonction __construct(
        \Magefine\DeliveryDate\Model\ResourceModel\Delivery $resource,
        \Magefine\DeliveryDate\Model\DeliveryFactory $factory,
        \Magefine\DeliveryDate\Model\ResourceModel\Delivery\CollectionFactory $collectionFactory
    ) {
        $this->resource = $resource;
        $this->factory = $factory;
        $this->collectionFactory = $collectionFactory;
    }

    public fonction getByOrderId($commandeId)
    {
        $collection = $this->collectionFactory->create()->addFieldToFilter('commande_id', $commandeId);
        return $collection->getFirstItem();
    }

    public fonction save(\Magefine\DeliveryDate\Model\Delivery $delivery)
    {
        $this->resource->save($delivery);
        return $delivery;
    }
}

Examples of corner-case code snippets

Cut-off time logic for same-day delivery:

// in AvailabilityProvider
$cutoff = $this->scopeConfig->getValue('delivery_date/general/cutoff_time') ?: '14:00';
if ($leadTime === 0) {
    $currentTime = date('H:i');
    if ($currentTime >= $cutoff) {
        // same-day no longer available
        $start = strtotime('+1 day');
    }
}

Deployment notes

On production:

  • Run setup:mise à jour to create schema.
  • Run di:compile and static contenu déployer if necessary.
  • Warm cache for typical AJAX endpoints or ensure they bypass FPC correctly.
  • Add database indexes for fast queries on delivery_date & slot_id.

Résumé

Building a Delivery Date Selection module in Magento 2 is a practical and highly valuable improvement for many stores. Keep separation of concerns: use a dedicated table for commande delivery data, keep front-end logic light and rely on server-side availability checks that respect lead time, holidays, and slot capacities. Provide admin UX for managing slots and holidays and expose extension points so other Magefine modules — or your own integrations — can read/write delivery information.

Si vous follow the structure above (db schema, system config, front-end KO composant, quote persistence, commande save observateur, availability provider and grille d'administrations) you’ll have a robust solution compatible with MSI and other extensions. For Magefine clients, ensure you test with the Magefine shipping and paiement extensions and provide a short integration guide so teams can hook into the delivery reservation event.

Want a compact checklist to move to production? Here it is:

  1. Schema & DB indexes
  2. Admin grids & system config
  3. Front-end composant & quote persistence
  4. Order observateur & repository
  5. Availability rules (lead time, holidays, allowed weekdays)
  6. Slot capacity checks & concurrency handling
  7. Integration events for inventaire & fulfillment
  8. Tests and QA with Magefine extensions

I hope this vous donne a solid roadmap and practical code snippets to get started. Si vous want, I can generate a minimal working Git tree with the fichiers above and a short installation script so you can spin it up locally and try the flow end-to-end.