Inzerce#
Modul Bazaar — inzeráty s dynamickými poli, placené tarify, hlídací pes a odpovědi.
Kód: app/UI/Api/Bazaar/, front app/UI/Front/Bazaar/, administrace
app/UI/Admin/Bazaar/.
Z čeho se modul skládá#
| Oblast | Hlavní entity | Poznámka |
|---|---|---|
| inzerát | Advert + relace AdvertParameterRel, AdvertCategoryRel, AdvertAssetRel, AdvertFileRel |
téměř všechna data jsou v relacích, ne ve sloupcích |
| kategorie | Category, CategoryText, CategoryPrice, CategoryParameterRel |
nested set |
| parametry | Parameter, ParameterGroup, ParameterRule + ParameterRuleCondition/Source/Target |
pravidla řídí, které pole se kdy zobrazí |
| placení | Tariff, TariffPrice, TariffQuickPrice, PaymentItem |
|
| interakce | Response, AdvertFollower, AdvertReport, AdvertStatistic |
|
| hlídací pes | Watchdog, WatchdogEmail + relace |
uložené hledání |
| moderace | BlacklistedWord |
Inzerát nemá pevná pole#
Název, popis, cena, e-mail i region jsou dynamické parametry (AdvertParameterRel).
Sloupce v tabulce jsou jen denormalizované kopie kvůli řazení a filtrování — ty se
totiž nad textovou hodnotou parametru dělat nedají.
| Klíč parametru | Denormalizovaný sloupec | Kdo ho plní |
|---|---|---|
price |
price (číslo, kvůli řazení a rozsahovému filtru) |
AdvertManager::beforeSave() z meta['parameters'] |
email |
email |
zápis inzerátu |
regionId |
region_id |
zápis inzerátu |
Přejmenování vyhrazeného klíče parametru tiše rozbije řazení
Nic to neohlásí — beforeSave() prostě nenajde, co má do sloupce zapsat. Řazení
podle ceny přestane fungovat a odpovědi přestanou chodit, protože se nemá kam
poslat e-mail. Vypadá to jako chyba výpisu.
Ceny v různých měnách se nepřepočítávají
Do sloupce jde holé číslo bez přepočtu. Řazení podle ceny tak míchá měny dohromady — 100 EUR se seřadí před 200 CZK.
Sloupec price má self-heal cron
/cron/bazaar/advert/recount-prices (à 10–15 minut) srovná nesrovnalosti po
importech a ručních zásazích do databáze. Je to pojistka, ne primární plnění.
Sloupec active počítá databáze#
cms_mod_bazaar_adverts.active je generovaný sloupec (VIRTUAL):
active = CASE WHEN approved_at IS NOT NULL
AND paid = 1 AND banned = 0 AND deleted = 0
AND deactivated = 0 AND sold = 0
THEN 1 ELSE 0 END
active nejde nastavit ani z administrace, ani z API
Mění se jedině zápisem některého z těch šesti příznaků. Entita ho má proto
označený insertable: false, updatable: false a navíc #[ExternalReadonly] —
bez toho druhého by setActive() přepsal hodnotu v paměti ještě před uložením
a odpověď by vrátila klientem vymyšlený stav místo skutečného.
Expirace v tom vzorci NENÍ
Prošlý inzerát zůstává active = 1; z výpisů ho vyřazuje až aplikační filtr. Kdo
si napíše vlastní dotaz jen na active = 1, dostane i inzeráty po expiraci.
Postup: přidávám dynamický parametr#
- Založ parametr a připoj ho ke kategorii (
CategoryParameterRel). - Volby výběru jsou děti parametru — parametr je kořen stromu, jeho potomci jsou jednotlivé volby.
- Rozhodni, jestli má mít denormalizovaný sloupec. Potřebuje ho jen to, podle čeho se řadí nebo filtruje rozsahem.
- Pravidla zobrazení (
ParameterRule) řeší, kdy se pole ukáže — podmínka nad jiným parametrem. - Prokliknij vložení i editaci inzerátu v té kategorii.
Smazání uzlu smaže celý podstrom
U parametru včetně všech jeho voleb — a s nimi hodnoty ve všech inzerátech, které tu volbu měly vyplněnou. Data se nedají vrátit.
Propsání cen nebo parametrů do podstromu je nevratné
Přepíše všechny podkategorie bez ptaní a bez zálohy. Na velkém stromu to znamená stovky přepsaných kategorií jedním kliknutím.
Denormalizovaná počítadla#
| Hodnota | Kdo ji srovná |
|---|---|
advert.price |
cron /cron/bazaar/advert/recount-prices |
category.advert_count |
cron /cron/bazaar/category/recount |
advert.email, advert.region_id |
zápis inzerátu |
Přepočet počtů je set-based UPDATE nad celým stromem — rodič počítá aktivní inzeráty ze svého celého podstromu, ne jen z přímých dětí.
Přepočet respektuje nastavení automatického schvalování
1/useAutoConfirmation mění, co se považuje za aktivní. Kdo počty ověřuje ručním
SQL, musí ho vzít v úvahu — jinak mu čísla nebudou sedět a bude hledat chybu
v cronu.
Hlídací pes#
Uložené hledání, které při novém inzerátu pošle e-mail.
Spouští se JEN při vložení inzerátu
Ne při editaci ani při přesunu do jiné kategorie. Inzerát, který se do kategorie dostane až později, hlídačům nikdy nepřijde — a nikomu nic nechybí, dokud se nezeptá inzerent.
Hlídač používá napevno zadaná id rodičovských parametrů
Po změně číselníků parametrů přestane sedět. Neprojeví se to chybou, jen tichým přestáním fungovat.
Zvýraznění a expirace#
toped_until řídí zvýraznění a řadí inzeráty ve výpisu. Prodloužení z platby je
monotonní — jen prodlužuje, nikdy nezkracuje. Ruční zásah v administraci umí obojí.
| Cron | Co dělá | Interval |
|---|---|---|
/cron/bazaar/advert/notice-expiration |
upozorní N dní před expirací (5/expirationNoticeDays), nastaví příznak, aby se neopakovalo |
denně |
/cron/bazaar/advert/delete-expired |
tiše označí deleted = 1 po M dnech od expirace (5/expirationDeleteDays) |
denně |
Notifikace#
Sledující dostanou upozornění POUZE při změně cenovky
Porovnává se trojice cena, typ ceny a měna. Změna názvu, popisu ani fotek neupozorní nikoho — a je to schválně, jinak by úprava překlepu rozeslala stovky e-mailů.
Editace v administraci neposílá žádné e-maily
Ani aktivační, ani hlídačům, ani sledujícím. Je to záměr: administrátor opravuje cizí inzerát a nemá tím spouštět komunikaci jeho jménem.
Elasticsearch#
Inzerce je jeden ze zdrojů Elasticsearch — reindex
/cron/bazaar/elastic/reindex, průběžná synchronizace /cron/bazaar/elastic/sync.
Hromadné operace mimo hooky index nevidí
Set-based UPDATE (přepočty, importy, ruční SQL) hook nespustí, takže se do
indexu nepropíše. Konzistenci drží až noční reindex — do té doby hledání vrací
stará data a nikde to není vidět.
Kam sáhnout#
| Chci | Kde |
|---|---|
| logiku inzerátu a denormalizace | app/UI/Api/Bazaar/Models/Managers/AdvertManager.php |
| přepočet počtů v kategoriích | app/UI/Api/Bazaar/Models/Managers/CategoryManager.php → recountAdverts() |
| hlídací pes | app/UI/Api/Bazaar/Models/Managers/Watchdog*Manager.php |
| definici entity a jejích příznaků | app/Core/Base/Shared/Bazaar/Models/Entities/Advert.php |
| cron úlohy | app/UI/Cron/Bazaar/Presenters/ |
| formulář inzerátu na frontu | app/UI/Front/Bazaar/Components/AdvertFormComponent/ |
Navazující kapitoly: Katalog firem · Elasticsearch · Filtry · Naplánované úlohy