ThysToys Bol Portal¶
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:
#!/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:
UpdateAllBolOffersListener selecteert alle gerelateerde producten op basis van BolAccount en aangepaste Rule- Jobs worden geplaatst in Horizon queue
bol-offersviaProductToBolOfferJob - ❌
ProductToBolOfferJobpast Rules niet correct toe viaapplyRules()methode
Stap 1: Rule aanpassen zonder events¶
Gebruik Rule::withoutEvents() om te voorkomen dat de defecte observer flow getriggerd wordt:
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.
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:
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:
#!/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-updateom 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:
-- 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_atis niet NULL - Offers die actief moeten blijven:
deleted_atis NULL - Correcte
fulfilment_delivery_codewaarden 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
php8.1 artisan bol:offers-force-update --bol_account=4 --product_reference=106249
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:
-- 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;
-- 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 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¶
-- 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 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;
-- 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¶
-- 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;
-- 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:
-- 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;
-- 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;
-- 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 offerbol: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.