Skip to content
V
For developers
Architecture, conventions, the core and security
Modules / Company directory

Company catalogue#

The Comcat module was created as a copy of Classifieds, so it shares almost the whole model: a company profile is an Advert with dynamic parameters, the category is a nested set, listing prices live on the category. The note about its creation is in docs/comcat-copy-from-bazaar.md.

Read Classifieds first

Everything said there about dynamic parameters, denormalised counters, display rules and the irreversible propagation into a subtree applies here too. This chapter describes only the differences.

Differences from Classifieds#

Classifieds Company catalogue
denormalised price yes no
the sold flag yes no
followers (AdvertFollower) yes no
saved-search alerts yes yes
settings index 5 6

The missing price is deliberate, not an omission

A company is not for sale. The prices are for the listing in a category, and those live on the category (CategoryPrice), not on the profile.

The difference propagates into the generated active column too — it has one condition fewer:

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

An SQL query copied over from classifieds will hit sold here

The column does not exist. Anyone carrying a finished query across gets an error — which is still the better outcome; the worse one is carried-over PHP that reaches for sold through a getter and receives null.

A profile has no fixed fields#

The name, company number, address, phone, description and opening hours are all parameters, exactly as on an advert.

Without parameters the form is empty

Before the first company can be created, parameters have to exist and be targeted at the category. The form throws no error — it simply has nothing to render, so it looks like an unfinished template.

Opening hours#

A field type of its own: a hidden field holding a structure that JavaScript fills in.

The opening hours can be lost silently

When the field is not rendered on save, the parameter collection is replaced without it — the same mechanism as with any other collection (Hydrators). Nothing reports it, which is why the template carries an explicit comment about it.

Categories and prices#

A nested set, a price matrix of region × payment gateway, propagation into a subtree.

Ticking "free" only HIDES the matrix, the prices are still saved

The fields are hidden by styling, not disabled. Unticking "free" reveals prices the administrator never knowingly entered.

Membership × a paid listing#

Access to a profile's detail in a paid category can be conditioned on the visitor's membership — which is something entirely different from the company's paid listing.

Payment Who pays What it unlocks
the listing the company that the profile is in the catalogue at all
membership the visitor that they can open the profile's detail

Two different payments, two different payers

Confusing them is the most common misunderstanding in this module. "The company paid and it still is not visible" means the visitor's membership is missing — not that the company's payment failed.

Settings#

The module has index 6 in the settings table (classifieds has 5). The form today writes only advertsPerPage, maxImageSize, expiration and automaticApproval.

Keys the code does not read were removed from the form — do not bring them back

Image dimensions, the resize type and watermarkFilename are not in the catalogue's settings: the variants are driven by assetVariants.neon (comcat-adverts) and the watermark by the global watermarkPath. The keys were deleted on 2026-08-13 (docs/sql/2026-08-13-settings-drop-unread-keys.sql). If somebody put them back into the form, the first save would recreate themsaveSettingForm() writes every listed key, including the ones nobody reads.

Scheduled tasks#

Endpoint What it does Recommended interval
/cron/comcat/category/recount recounts companies per category every 10–15 min
/cron/comcat/advert/notice-expiration warns before a listing expires daily
/cron/comcat/advert/delete-expired marks deleted = 1 after expiry daily
/cron/comcat/watchdog-email/send sends the saved-search alerts every 5–10 min
/cron/comcat/watchdog-email/digest the summary digest daily in the evening
/cron/comcat/elastic/sync incremental index synchronisation every 10 min
/cron/comcat/elastic/reindex a full reindex nightly
/cron/comcat/sitemap/{category,advert} sitemaps nightly

Without the expiry cron, listings never expire

Companies that stopped paying stay in the catalogue — and because active knows nothing about expiry, they keep being displayed normally.

Where to look#

I want Where
the profile logic app/UI/Api/Comcat/Models/Managers/AdvertManager.php
the count recalculation app/UI/Api/Comcat/Models/Managers/CategoryManager.php
the module settings app/UI/Admin/Comcat/Presenters/SettingPresenter.php
the cron tasks app/UI/Cron/Comcat/Presenters/
the note about the module's creation docs/comcat-copy-from-bazaar.md

Follow-up chapters: Classifieds · Elasticsearch · Scheduled tasks