Skip to content

ThysToys Bol Portal

Terug naar ThysToys

De ThysToys Bol Portal is een Laravel-applicatie die de integratie met Bol.com verzorgt voor geautomatiseerde orderverwerking en productbeheer.

Aspect Details
Type Integratie
Bronsysteem ThysToys Shop Platform
Doelsysteem Bol.com
Repository thystoys/thystoys-bol-api-laravel-2021
Framework Laravel (PHP 8.1, sinds augustus 2021)

Omgevingen

Omgevingscode: TTBOL Prod

Aspect Details
URL https://bol.thystoys.nl
Forge Site #1448818
Server FL09FOR-TYS05

Omgevingscode: TTBOL Test

Aspect Details
URL https://thystoys-bol.9test.nl
Forge Site #1437688
Server FL09FOR-TYS05

Bol Accounts

In de applicatie worden verschillende Bol accounts beheerd met unieke configuraties:

Account ID Naam
1 Bol NL
2 Bol BE
3 Bol BE (Waterplays account)
4 BoL NL (Trampolinewinkel Be Account)
5 Bol Coolzwembad NL
6 Bol Coolzwembad BE

Procedures

Producten zonder bol_offer opnieuw toevoegen

Deze procedure wordt gebruikt om producten die soft-deleted zijn maar geen bol_uuid hebben, opnieuw toe te voegen aan Bol.com.

Bol Account Context

Deze procedure is specifiek voor Bol Account ID 4 (BoL NL - Trampolinewinkel Be Account)

Stap 1: Backup maken

Maak eerst een backup van de te verwijderen records:

-- Soft deleted bol_offers zonder bol_uuid voor BolAccount 4
SELECT *
FROM `thystoys_bol_2021_prod`.`bol_offers`
WHERE `bol_uuid` IS NULL
  AND `deleted_at` IS NOT NULL
  AND `bol_account_id` = 4;

Backup opslaan

Export deze resultaten via TablePlus naar een .sql bestand met INSERT statements voor disaster recovery.

Stap 2: Records verwijderen

-- Definitief verwijderen van soft-deleted records zonder bol_uuid
DELETE FROM `thystoys_bol_2021_prod`.`bol_offers`
WHERE `bol_uuid` IS NULL
  AND `deleted_at` IS NOT NULL
  AND `bol_account_id` = 4;

Stap 3: Selectie vaststellen

Bepaal welke producten opnieuw toegevoegd moeten worden en sla dit op in een CSV:

SELECT
    products.reference,
    products.title,
    bol_offers.`fulfilment_delivery_code`
FROM products
JOIN bol_offers
    ON bol_offers.`reference` = products.`reference`
    AND bol_offers.`bol_account_id` = 4
WHERE products.`active_bol_tw_be` = 1
  AND products.`stock_vdm` > 0
  AND products.`product_status` LIKE '%vandermeulen%'
  AND bol_offers.`fulfilment_delivery_code` <> 'MijnLeverbelofte'
  AND bol_offers.`bol_uuid` IS NULL;

Stap 4: Bash script genereren

Genereer artisan commando's voor alle producten:

SELECT
    CONCAT(
        'php8.1 artisan bol:offer-update-or-create --bol_account=4 --product_reference=',
        products.reference
    ) AS `command`
FROM products
JOIN bol_offers
    ON bol_offers.`reference` = products.`reference`
    AND bol_offers.`bol_account_id` = 4
WHERE products.`active_bol_tw_be` = 1
  AND products.`stock_vdm` > 0
  AND products.`product_status` LIKE '%vandermeulen%'
  AND bol_offers.`fulfilment_delivery_code` <> 'MijnLeverbelofte'
  AND bol_offers.`bol_uuid` IS NULL;

Gebruik een editor zoals Zed om het resultaat te verwerken tot een bash script:

TT-1393-producten-ZONDER-bol_offer.sh
#!/bin/bash

echo "Product reference: 5647"
php8.1 artisan bol:offer-update-or-create --bol_account=4 --product_reference=5647
echo "."

echo "Product reference: 23957"
php8.1 artisan bol:offer-update-or-create --bol_account=4 --product_reference=23957
echo "."

echo "Product reference: 24249"
php8.1 artisan bol:offer-update-or-create --bol_account=4 --product_reference=24249
echo "."

echo "Product reference: 29945"
php8.1 artisan bol:offer-update-or-create --bol_account=4 --product_reference=29945
echo "."

Stap 5: Uitvoeren op server

Start het script in een screen sessie om disconnects te voorkomen:

# Login op server via SSH
# forge@FL09FOR-TYS05 ~/bol.thystoys.nl

# Start een screen sessie
screen -S TT-1393-bol-update-zonder-bol-offer

# Voer het script uit met logging
bash TT-1393-producten-ZONDER-bol_offer.sh >> TT-1393-producten-ZONDER-bol_offer.log

Stap 6: Progressie monitoren

Monitor de voortgang vanuit een andere terminal:

# Detach uit de screen sessie: CTRL+A, D

# Volg de log file
tail -f TT-1393-producten-ZONDER-bol_offer.log

Screen sessies beheren

  • Lijst tonen: screen -ls
  • Hervatten: screen -r TT-1393-bol-update-zonder-bol-offer
  • Detach: CTRL+A → D

Bol Rules aanpassen en toepassen

Deze procedure beschrijft hoe je Bol Rules (Rule eloquent model) aanpast wanneer de normale observer flow niet correct werkt.

Bekende issue met Rule observers

De normale procedure waarbij een BolRule een observer heeft op saved en deleted events, werkt momenteel NIET correct!

De UpdateAllBolOffers Listener maakt wel een selectie van gerelateerde producten en dispatcht deze naar de ProductToBolOfferJob, maar de Rules worden niet correct toegepast op de BolOffer records.

Oorzaak: Onbekend waarom $productBolOfferWithRules = $this->productBolOffer->applyRules($this->rules) in de Job niet goed werkt.

Architectuur probleem

Rule.php - Observer events

protected $dispatchesEvents = [
    'saved'   => RuleSavedEvent::class,
    'deleted' => RuleDeletedEvent::class,
];

EventServiceProvider - Listener koppeling

RuleSavedEvent::class => [
    UpdateAllBolOffers::class,
],

Wat er gebeurt:

  1. UpdateAllBolOffers Listener selecteert alle gerelateerde producten op basis van BolAccount en aangepaste Rule
  2. Jobs worden geplaatst in Horizon queue bol-offers via ProductToBolOfferJob
  3. ❌ ProductToBolOfferJob past Rules niet correct toe via applyRules() methode

Stap 1: Rule aanpassen zonder events

Gebruik Rule::withoutEvents() om te voorkomen dat de defecte observer flow getriggerd wordt:

Voorbeeld: VanderMeulen stock threshold rule
use App\Models\Rule;
use App\Models\BolAccount;

$bolAccount = BolAccount::findOrFail(4);

Rule::withoutEvents(function () use ($bolAccount) {
    Rule::updateOrCreate([
        'bol_account_id' => $bolAccount->id,
        'name'           => "Delete 'vandermeulen' offers where 'stock' is less than threshold product offers",
    ], [
        'priority'     => 9,
        'description'  => "Deletes product offers where 'stock_vdm' is applied and 'stock_vdm' is less than VDM threshold. Using 'bol_offer_stock_amount' because `active_bol_forced` applies to the `bol_offer_stock_amount`.",
        'condition'    => new ContainerCondition(
            value: collect([
                new EqualCondition(
                    attribute: 'product_status',
                    value: 'vandermeulen'
                ),
                new LessThanOrEqualCondition(
                    attribute: 'stock',
                    value: 2
                ),
                new LessThanOrEqualCondition(
                    attribute: 'bol_offer_stock_amount',
                    value: 9
                ),
            ])
        ),
        'consequences' => collect([
            new DeleteConsequence(),
        ]),
    ]);
});

Rule wordt opgeslagen zonder events

Door Rule::withoutEvents() te gebruiken worden de saved en deleted events niet afgevuurd en wordt de defecte observer flow omzeild.

Stap 2: Getroffen producten selecteren

Maak een best-effort selectie van producten die geraakt worden door de Rule wijziging:

Best-effort selectie

Deze selectie is niet 100% compleet. Om echt alle mogelijke effecten te vangen zou je ALLE producten moeten updaten, want potentieel kan ieder product een relatie hebben tot de Rule.

Voor praktische doeleinden maken we een gerichte selectie op basis van de Rule condities.

Selectie VanderMeulen producten zonder juiste fulfilment
SELECT
    products.reference,
    products.title,
    bol_offers.`fulfilment_delivery_code`
FROM products
JOIN bol_offers
    ON bol_offers.`reference` = products.`reference`
    AND bol_offers.`bol_account_id` = 4
WHERE products.`active_bol_tw_be` = 1
  AND products.`stock_vdm` > 0
  AND products.`product_status` LIKE '%vandermeulen%'
  AND bol_offers.`fulfilment_delivery_code` <> 'MijnLeverbelofte'
  AND bol_offers.`bol_uuid` IS NULL;

Variabelen in de query aanpassen:

  • bol_account_id: Pas aan naar het juiste BolAccount
  • WHERE condities: Stem af op de Rule condities die je hebt aangepast

Stap 3: Bash script genereren

Genereer artisan commando's voor geforceerde offer updates:

Genereer update commando's
SELECT
    CONCAT('php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=',
           products.reference) AS `command`
FROM products
JOIN bol_offers
    ON bol_offers.`reference` = products.`reference`
    AND bol_offers.`bol_account_id` = 4
WHERE products.`active_bol_tw_be` = 1
  AND products.`stock_vdm` > 0
  AND products.`product_status` LIKE '%vandermeulen%'
  AND bol_offers.`fulfilment_delivery_code` <> 'MijnLeverbelofte'
  AND bol_offers.`bol_uuid` IS NULL;

Gebruik een editor zoals Zed om het resultaat te verwerken tot een bash script:

TT-1393-update-bol_offers.sh
#!/bin/bash

echo "Product reference: 5647"
php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=5647
echo "."

echo "Product reference: 23957"
php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=23957
echo "."

echo "Product reference: 24249"
php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=24249
echo "."

echo "Product reference: 29945"
php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=29945
echo "."

Script structuur

  • Elke update krijgt een duidelijke echo met product reference
  • Een punt (.) na elke update voor visuele scheiding in de logs
  • Gebruik bol:offers-force-update om Rules opnieuw toe te passen

Stap 4: Uitvoeren op server

Start het script in een screen sessie om disconnects te voorkomen:

# Login op server via SSH
# forge@FL09FOR-TYS05 ~/bol.thystoys.nl

# Start een screen sessie
screen -S TT-1393-update-bol_offers

# Voer het script uit met logging
bash TT-1393-update-bol_offers.sh >> TT-1393-update-bol_offers.log

Stap 5: Progressie monitoren

Monitor de voortgang vanuit een andere terminal:

# Detach uit de screen sessie: CTRL+A, D

# Volg de log file
tail -f TT-1393-update-bol_offers.log

# Of gebruik watch voor real-time updates
watch -n 2 "tail -20 TT-1393-update-bol_offers.log"

Screen sessies beheren

  • Lijst tonen: screen -ls
  • Hervatten: screen -r TT-1393-update-bol_offers
  • Detach: CTRL+A → D
  • Beëindigen: CTRL+D (vanuit de screen sessie)

Stap 6: Validatie

Controleer na afloop of de Rules correct zijn toegepast:

Controleer resultaat
-- Check of de offers correct zijn geüpdatet
SELECT
    products.reference,
    products.product_status,
    products.stock_vdm,
    bol_offers.fulfilment_delivery_code,
    bol_offers.bol_offer_stock_amount,
    bol_offers.deleted_at
FROM products
JOIN bol_offers
    ON bol_offers.reference = products.reference
    AND bol_offers.bol_account_id = 4
WHERE products.reference IN (5647, 23957, 24249, 29945)
ORDER BY products.reference;

Verwachte resultaten:

  • Offers die voldoen aan delete conditions: deleted_at is niet NULL
  • Offers die actief moeten blijven: deleted_at is NULL
  • Correcte fulfilment_delivery_code waarden volgens de Rules

Veelgebruikte MySQL queries

Producten met Bol offers selecteren

Genereer artisan commando's voor producten met actieve offers:

Bol Account ID 4 - Trampolinewinkel Be Account

Artisan commando voorbeeld
php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=106249
Query: Producten met offers
SELECT
    CONCAT(
        'php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=',
        products.reference
    ) AS `command`
FROM products
JOIN bol_offers
    ON bol_offers.`reference` = products.`reference`
    AND bol_offers.`bol_account_id` = 4
WHERE products.`active_bol_tw_be` = 1
  AND products.`stock_vdm` > 0
  AND products.`product_status` LIKE '%vandermeulen%'
  AND bol_offers.`fulfilment_delivery_code` <> 'MijnLeverbelofte';

Bol Offer Export validatie

Queries voor het valideren van offer exports:

Ontbrekende offers in database
-- Vind offers uit export die nog niet in de database staan
SELECT *
FROM _tmp_offer_export
LEFT JOIN bol_offers
    ON bol_offers.ean = _tmp_offer_export.ean
    AND bol_offers.bol_account_id = _tmp_offer_export.bol_account_id
WHERE bol_offers.`uuid` IS NULL;
NL offers zonder BE equivalent
-- Vind NL offers die geen BE variant hebben
SELECT *
FROM _tmp_offer_export AS offers_nl
LEFT JOIN _tmp_offer_export AS offers_be
    ON offers_be.bol_account_id = 3
    AND offers_be.ean = offers_nl.ean
WHERE offers_nl.bol_account_id = 4
  AND offers_be.id IS NULL;
Genereer import commando's
-- Genereer artisan commando's voor import van ontbrekende offers
SELECT
    CONCAT(
        'php8.1 artisan bol:offer-get 1 "',
        _tmp_bol_offers2.`uuid`,
        '" --create'
    ) AS `command`
FROM _tmp_bol_offers
LEFT JOIN bol_offers
    ON bol_offers.ean = _tmp_bol_offers.ean
JOIN _tmp_bol_offers2
    ON _tmp_bol_offers2.ean = _tmp_bol_offers.ean
WHERE bol_offers.`uuid` IS NULL;

Notificaties en monitoring

Gefaalde Bol offer jobs
-- Tel ongelezen notificaties per notifiable
SELECT
    COUNT(1) AS `aantal`,
    notifiable_id,
    notifiable_type,
    `type`
FROM notifications
WHERE `type` = 'bolOfferJobFailed'
  AND read_at IS NULL
GROUP BY notifiable_id, notifiable_type, `type`;

Rules vergelijken tussen accounts

Vergelijk rules tussen NL en BE
-- Vergelijk rules tussen Bol NL (account 1) en Bol BE (account 2)
SELECT
    rules_nl.`id` AS `id_NL`,
    rules_nl.`name` AS `name_NL`,
    rules_nl.`order` AS `order_NL`,
    rules_be.`id` AS `id_BE`,
    rules_be.`name` AS `name_BE`,
    rules_be.`order` AS `order_BE`
FROM `rules` AS `rules_nl`
LEFT JOIN `rules` AS `rules_be`
    ON rules_be.`name` = rules_nl.`name`
    AND rules_be.bol_account_id = 2
WHERE rules_nl.bol_account_id = 1;
Alle rules gesorteerd
-- Bekijk alle rules voor account 1 en 2, gesorteerd op naam
SELECT *
FROM `rules`
WHERE bol_account_id IN (1, 2)
ORDER BY `name`, `bol_account_id`;

Producten met voorraad zonder offer

Genereer commando's voor producten met voorraad
-- Vind actieve producten met voorraad die nog geen offer hebben
SELECT
    CONCAT(
        'php artisan bol:offer-update-or-create --bol_account=4 --product_id=',
        products.reference
    ) AS `command`
FROM products
LEFT JOIN bol_offers
    ON bol_offers.reference = products.reference
    AND bol_offers.bol_account_id = 4
WHERE products.active_bol_tw_be = 1
  AND (
      products.stock > 0
      OR products.stock_vdm > 0
      OR products.stock_berg > 0
      OR products.stock_exit > 0
      OR products.stock_avyna > 0
      OR products.stock_salta > 0
      OR products.stock_pragma > 0
      OR products.stock_volare > 0
      OR products.stock_akrobat > 0
      OR products.stock_forced_available = 1
  )
  AND bol_offers.`uuid` IS NULL;
Specifiek product opzoeken
-- Zoek offers voor specifiek EAN
SELECT *
FROM bol_offers
JOIN products
    ON products.reference = bol_offers.reference
WHERE bol_offers.ean IN ('8712051139081')
  AND bol_offers.bol_account_id = 4;

Bol BE offers synchronisatie

Deze queries worden gebruikt voor het synchroniseren van offers tussen de portal en Bol.com BE:

Ontbrekende UUIDs identificeren
-- Vind Bol BE offers die wel op Bol.com staan maar geen UUID in database hebben
SELECT bol_offers.*
FROM bol_offers
LEFT JOIN _tmp_offers_be
    ON bol_offers.ean = _tmp_offers_be.ean
WHERE bol_offers.bol_account_id = 3
  AND _tmp_offers_be.offerId IS NOT NULL
  AND bol_offers.bol_uuid IS NULL;
Validatie van gekoppelde offers
-- Valideer offers met correcte UUID koppeling
SELECT *
FROM bol_offers
LEFT JOIN _tmp_offers_be
    ON bol_offers.ean = _tmp_offers_be.ean
    AND bol_offers.bol_uuid = _tmp_offers_be.offerId
WHERE bol_offers.bol_account_id = 3
  AND _tmp_offers_be.offerId IS NOT NULL;
UUID synchronisatie uitvoeren
-- Update ontbrekende bol_uuid waarden voor Bol BE
UPDATE bol_offers AS bo
LEFT JOIN _tmp_offers_be AS t
    ON bo.ean = t.ean
SET
    bo.bol_uuid = t.offerId,
    bo.`updated_at` = NOW()
WHERE bo.bol_account_id = 3
  AND t.offerId IS NOT NULL
  AND bo.bol_uuid IS NULL;

Tijdelijke tabellen

De _tmp_offers_be en _tmp_offer_export tabellen moeten handmatig aangemaakt worden met de juiste exports vanuit Bol.com voordat deze queries uitgevoerd kunnen worden.


Veelgestelde vragen

Wat is het verschil tussen bol:offer-update-or-create en bol:offers-force-update?
  • bol:offer-update-or-create: Creëert een nieuwe offer of update een bestaande offer
  • bol:offers-force-update: Forceert een update van een bestaande offer, zelfs als er geen wijzigingen zijn
Waarom gebruiken we screen sessies?

Screen sessies zorgen ervoor dat lange processes blijven draaien zelfs als je SSH verbinding verbroken wordt. Dit is essentieel voor batch updates die uren kunnen duren.

Hoe vaak moeten offers gesynchroniseerd worden?

De portal heeft Laravel Horizon jobs die automatisch draaien. Handmatige synchronisatie is alleen nodig bij specifieke problemen of bulk updates.


Gerelateerde documentatie