Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
Moduly / Katalog firem

Katalog firem#

Modul Comcat vznikl kopií z Inzerce, takže sdílí skoro celý model: profil firmy je Advert s dynamickými parametry, kategorie je nested set, ceny za zápis jsou na kategorii. Zápisek o vzniku je v docs/comcat-copy-from-bazaar.md.

Nejdřív si přečtěte Inzerci

Co platí tam o dynamických parametrech, denormalizovaných počítadlech, pravidlech zobrazení a nevratném propsání do podstromu, platí i tady. Tahle kapitola popisuje jen rozdíly.

Rozdíly proti Inzerci#

Inzerce Katalog firem
denormalizovaná price ano ne
příznak sold ano ne
sledující (AdvertFollower) ano ne
hlídací pes ano ano
index nastavení 5 6

Chybějící cena je záměr, ne opomenutí

Firma se neprodává. Ceny jsou za zápis do kategorie, a ty jsou na kategorii (CategoryPrice), ne na profilu.

Rozdíl se propisuje i do generovaného sloupce active — má o jednu podmínku míň:

-- cms_mod_comcat_adverts.active (VIRTUAL)
active = CASE WHEN approved_at IS NOT NULL AND confirmed_at IS NOT NULL
              AND paid = 1 AND banned = 0 AND deleted = 0 AND deactivated = 0
         THEN 1 ELSE 0 END

Kopírovaný SQL dotaz z inzerce tu na sold narazí

Sloupec neexistuje. Kdo si přenese hotový dotaz, dostane chybu — což je ještě ta lepší varianta; horší je přenesený PHP kód, který na sold sáhne přes getter a dostane null. Přenesený filtr EqualFilter('sold', false) projde sestavením i PHPStanem a spadne až za běhu na QueryException („has no field or association named sold“), a to celou stránku, na které je výpis. Výchozí sada viditelnosti v LastAdvertList proto sold nemá.

Sdíleno s Inzercí#

Tyto části nemají v katalogu vlastní kopii, kód je společný (traity v app/Core/Traits/):

Část Sdílený kód Popis
zakázaná slova Api System\BlacklistedWordManager, systémová tabulka cms_system_blacklisted_words modulová tabulka cms_mod_comcat_blacklisted_words už neexistuje; Inzerce
pravidla parametrů při Api zápisu AdvertParameterPolicyTrait, ParameterRuleEvaluator, AdvertParameterRuleContextRepositoryTrait Inzerce
nahlášení profilu ReportedFlagSyncTrait (Api), ReportModerationPresenterTrait (administrace) Inzerce
správa profilu hostem ManageKeyTrait nad ManageKeyAwareInterface Odkaz s klíčem

Profil nemá pevná pole#

Název, IČO, adresa, telefon, popis i otevírací doba jsou parametry, stejně jako u inzerátu.

Bez parametrů je formulář prázdný

Než se dá založit první firma, musí existovat parametry a musí být nacílené na kategorii. Formulář nevyhodí chybu — prostě nemá co vykreslit, takže to vypadá jako nedokončená šablona.

Cílení parametrů na kategorii, propsání do podkategorií (meta['setParametersToChildren']) i propsání cen (meta['setPricesToChildren']) funguje jako cílení v Inzerci: chybějící meta['parameterRels'] cílení nemění, prázdný seznam ho smaže. Katalog firem má vlastní kopii traitů v app/UI/Api/Comcat/Models/Managers/Traits/CategoryManager/ a chování drží tests/Unit/Comcat/Api/CategoryParameterRelsMetaTriStateTest.php.

Otevírací doba#

Vlastní typ pole: skryté pole s uloženou strukturou, které naplňuje JavaScript.

Otevírací doba se může tiše ztratit

Když se pole při uložení nevykreslí, kolekce parametrů se nahradí bez něj — tatáž mechanika jako u každé jiné kolekce (Hydrátory). Nic to neohlásí a v šabloně je to proto výslovně okomentované.

Formulář profilu na webu#

Front formulář firemního profilu funguje jako formulář inzerátu na frontu: předvyplnění z účtu podle klíče parametru přes getEntityValues() z LoadingTrait, parameterRels jen při aspoň jednom vyplněném řádku, kategorie v meta['categoryIds']. Navíc proti inzerci předvyplní parametr s klíčem contactName (jméno a příjmení v jednom poli).

Prázdné pole parametru se přeskočí. Zbyl-li aspoň jeden vyplněný řádek, kolekce se nahradí celá a hodnoty vyprázdněných polí smaže orphanRemoval. Vyprázdnit z webu všechny parametry naráz nejde: odeslání bez jediného vyplněného parametru kolekci neposílá a uložené hodnoty zůstanou.

Kategorie a ceny#

Nested set, matice cen region × platební brána, propsání do podstromu.

Zaškrtnuté „zdarma“ jen SKRYJE matici, ceny se uloží dál

Pole jsou schovaná stylem, ne vypnutá. Odškrtnutím „zdarma“ se objeví ceny, které tam admin nikdy vědomě nezadal.

Členství × zaplacený zápis#

Detail profilu v placené kategorii se dá podmínit členstvím návštěvníka — což je něco úplně jiného než zaplacený zápis firmy.

Platba Kdo platí Co odemyká
zápis firma že profil v katalogu vůbec je
členství návštěvník že si detail profilu může zobrazit

Dvě různé platby, dva různí plátci

Záměna je nejčastější nedorozumění v tomhle modulu. „Firma zaplatila, a stejně to není vidět" znamená, že chybí členství návštěvníka — ne že selhala platba firmy.

Řazení podle hodnocení#

Sortbox výpisu (Advert/list.latte) nabízí tři volby a prepareOrderBy() má pro každou vlastní větev:

/o-N Volba Řazení
1 Nejnovější insertDatetime ↓ (i výchozí větev)
2 Nejčtenější viewsDetail ↓
3 Nejlépe hodnocené ratingThread.ratingAvg ↓

Před volbou vždy stojí topované profily (topedUntil > NOW() ↓) a za ní id jako jednoznačný druhý klíč (OrderByHelper::withIdTiebreak()), jinak by se shodné hodnoty stránkovaly náhodně.

Nabízená volba bez vlastní větve tiše řadí jako „Nejnovější“

Volba, kterou sortbox nabízí a switch v prepareOrderBy() nezná, propadne do default. Uživatel si vybere řazení a nic se nezmění. Novou volbu přidávej do obou míst zároveň.

Výpis firem nabízí řazení „Nejlépe hodnocené“ (/o-3, ratingThread.ratingAvg sestupně, AdvertListTrait::prepareOrderBy()); inzerce ho nemá. Ze skaláru ratingThreadId umí Doctrine jen WHERE, řadit podle sloupce cizí tabulky jde jen přes asociaci. Api entita Advert proto mapuje sloupec vlákna hodnocení podruhé, asociací $ratingThread, a ta nese #[ExternalReadonly] stejně jako skalár (Atributy pro zápis sloupců). Profil bez hodnocení z výpisu nevypadne, asociace se připojuje LEFT JOINem.

Asociace bez #[ExternalReadonly] otevře přepojení hodnocení

Externí klient by poslal data.ratingThread = {"id": X}, obešel zámek skaláru a připojil profil k cizímu vláknu hodnocení, tedy k cizímu průměru a recenzím.

Odpovědi: příjemce a přečtení#

Odpovědi na firemní profil fungují stejně jako v Inzerci: šablona CC_ADVERT_RESPONSE nese tlačítko {$markReadLink}, veřejná stránka Front:Comcat:Response:markRead má id MARK_READ_SLUG = 2012, výpis LIST_SLUG = 208 a detail DETAIL_SLUG = 209 (dřív 159).

Nastavení#

Modul má v tabulce nastavení index 6 (inzerce má 5). Formulář (app/UI/Admin/Comcat/Presenters/SettingPresenter.php) zapisuje tyto klíče:

Klíč Pole Kdo ho čte
advert.perPage, maxImageSize, advert.expiration.days číslo 1–100, 1–100 (MB), 1–3650 výpis; limit uploaderu, když ho nemá parametr obrázků; výpočet expirace, když ji neurčí kategorie
advert.approval.auto přepínač schválení správcem při vložení
advert.confirmation.required.logged, advert.confirmation.required.guest přepínač potvrzení autorem odkazem z e-mailu, jen při vložení; výklad viz Inzerce
geocoding.precise_address přepínač AdvertRegionAddressTrait, viz Inzerce
advert.statistics.enabled přepínač Api AdvertManager; vypnuto = denní statistiky pro graf v editaci se nezapisují, počítadla zobrazení běží dál
advert.expiration.noticeDays, advert.expiration.deleteDays 0–365, 0–3650 (0 = vypnuto) crony notice-expiration, delete-expired
watchdog.perPage 1–100 front výpis hlídačů
watchdog.email.mode 0 vypnuto, 1 hned cronem send, 2 denní souhrn cronem digest, 3 hned při vložení bez cronu WatchdogManager, cron WatchdogEmailPresenter
watchdog.email.maxAttempts, watchdog.email.retentionDays 1–20, 1–3650 rozesílání a úklid fronty hlídačů

Api čte skupinu 6 napevno (getSettingContext()->get(6, …)). Konstanta MODULE = 6 v presenteru proto musí sedět, jinak formulář ukládá do skupiny, kterou nikdo nečte, a změny nastavení se tiše neprojeví.

Klíče, které kód nečte, byly z formuláře odstraněny — nevracejte je

Rozměry obrázků, typ zmenšení, watermarkFilename, responsesPerPage ani afterAddAdvertRedirectOn v nastavení katalogu nejsou, katalog je nečte: varianty se čtou z DB (použití comcat-adverts, viz Varianty obrázků) a vodoznak je globální watermarkPath. Přiřazení variant se edituje v části Obrázky pod formulářem nastavení. Kdyby někdo staré klíče do formuláře vrátil, první uložení je založí zpátky — saveSettingForm() zapisuje všechny vyjmenované klíče.

Naplánované úlohy#

Endpoint Co dělá Doporučený interval
/cron/comcat/category/recount přepočet počtů firem v kategoriích à 10–15 minut
/cron/comcat/advert/notice-expiration upozorní před vypršením zápisu denně
/cron/comcat/advert/delete-expired označí deleted = 1 po expiraci denně
/cron/comcat/watchdog-email/send rozešle hlídače à 5–10 minut
/cron/comcat/watchdog-email/digest souhrnný přehled denně večer
/cron/comcat/watchdog-email/purge-old smaže odeslané a zrušené řádky fronty hlídačů starší než watchdog.email.retentionDays (výchozí 90 dní), čekající nikdy; podrobně Inzerce denně
/cron/comcat/elastic/sync průběžná synchronizace indexu à 10 minut
/cron/comcat/elastic/reindex úplný reindex denně v noci
/cron/comcat/sitemap/{category,advert} sitemapy denně v noci

Profil po expiraci zmizí z výpisu i z detailu hned, bez ohledu na crony: active expiraci nezná, ale výpis i detail přidávají podmínku expiration_date >= dnes. Detail ho pak ukáže jen majiteli s upozorněním „po expiraci“.

Bez cronů expirace firma nedostane upozornění a prošlý profil se neuklidí

Bez notice-expiration firma před vypršením zápisu nedostane e-mail a nemá jak včas prodloužit. Bez delete-expired zůstane prošlý profil v databázi a v „Mých firmách“ navždy. Totéž nastane, když je advert.expiration.noticeDays nebo deleteDays 0.

Kam sáhnout#

Chci Kde
logiku profilu app/UI/Api/Comcat/Models/Managers/AdvertManager.php
přepočet počtů app/UI/Api/Comcat/Models/Managers/CategoryManager.php
řazení a filtry výpisu firem app/UI/Front/Comcat/Presenters/Traits/Lists/AdvertListTrait.php
nastavení modulu app/UI/Admin/Comcat/Presenters/SettingPresenter.php
formulář profilu na webu app/UI/Front/Comcat/Components/AdvertFormComponent/
cron úlohy app/UI/Cron/Comcat/Presenters/
zápisek o vzniku modulu docs/comcat-copy-from-bazaar.md

Navazující kapitoly: Inzerce · Elasticsearch · Naplánované úlohy

Nastavení cookies