Task: Generate calendar_dates Qt facet and wire Browse Holidays action
Table of Contents
This page documents a task in the Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Give calendar_date (the calendar_dates junction) an
ores.cpp.qt facet – client model, read-only paginated list dialog,
controller – scoped to a calendar_code parent key via the
has_readonly_paginated_list=/=has_parent_scoped_list knobs landed
in the parent-scoping task, and wire a "Browse Holidays" toolbar
action on CalendarDetailDialog that opens it for the currently
open calendar. Closes the deferred acceptance item from
B2888D7D.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Calendar entity follow-ups: date picker, list-pagination fix, QuantLib materialization |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-29 |
Acceptance
[X]ores.refdata.calendar_date.orggains a** Qtdrawer (under* C++, mirroringores.refdata.book.org's** Qtdrawer shape) setting:has_readonly_paginated_list: true,:has_parent_scoped_list: true,:parent_key_field: calendar_code,:parent_key_param: calendarCode(or equivalent camelCase), plus the required domain/protocol pointers (:get_request_class: refdata::messaging::get_calendar_dates_by_calendar_request,:get_response_class: refdata::messaging::get_calendar_dates_by_calendar_response,:get_message_type:,:collection_name: calendar_dates,:item_var: calendar_date,:key_field:, MDI chrome (:settings_group:,:window_title: Browse Holidays,:icon:).[X]Generatingcalendar_date'sores.cpp.qtfacet produces a workingClientCalendarDateModel,CalendarDateMdiWindow, andCalendarDateController– read-only (no add/edit/delete/history, perhas_readonly_paginated_list), paginated, and scoped to the parentcalendar_codeon every fetch (perhas_parent_scoped_list), per the design in1C673C53.[X]RefdataPluginconstructs aCalendarDateControlleralongside the other refdata controllers (same constructor-injection pattern ascalendarController_=/=businessCentreController_).[X]CalendarDetailDialoggains a "Browse Holidays" toolbar action (same shape as the existingregenerateAction_=/=onRegenerateClickedpair added in #1707), emitting a newbrowseHolidaysRequested(QString calendarCode)signal instead of reaching for a sibling controller directly (dialogs don't hold controller references today – seecalendarSaved=/=calendarDeletedfor the existing signal-out pattern).[X]CalendarControllerrelays that signal outward (mirroringBookController::showBookStatusesRequested's cross-domain relay shape), andRefdataPluginconnects it tocalendarDateController_->openForParent(calendarCode).[X]Each row of the opened "Browse Holidays" list shows date, is-business-day, and source, perB2888D7D's original acceptance wording and thecalendar_date.org"Table display" table.[X]Verified via--diffagainst an existing entity with the new knobs left unset showing zero unrelated drift (same approach as1C673C53's verification), plus a real, working generation forcalendar_dateitself (first entity to actually exercise these knobs end to end).[X]Full local build green; manual QA scenario opening "Browse Holidays" for a seeded QuantLib calendar (e.g. TARGET) and confirming paginated, read-only holiday rows.
Plan
(Implementation strategy. Written when work starts; key decisions
are distilled into the parent story's * Decisions at close, but the
plan itself stays — it is the historical record of what we did.)
Investigation findings
calendar_date's service/protocol/nats-handler layers already exist (landed in the extra_list_requests task):refdata::messaging::get_calendar_dates_by_calendar_request/responseinores.refdata.api/messaging/calendar_date_protocol.hpp, NATS subjectrefdata.v1.calendar_dates.list_by_calendar_code, fieldscalendar_code=/=offset=/=limiton the request,calendar_dates=/=total_available_count=/=success=/=messageon the response – this is theget_request_class=/=get_response_classpair the new Qt facet points at.ores.refdata.calendar_date.org(the junction model,projects/ores.refdata/modeling/ores.refdata.calendar_date.org) has* Left(calendar_code, already:list_by: true),* Right(date),* Columns(is_business_day,source),* SQL,* Repository,* C++(flags/includes/conventions/table display) – but no** Qtdrawer yet.calendar_datewill be the first junction model to get one; no existing junction has a Qt facet to copy verbatim, butcore.pybuilds theqtdict identically regardless of domain_entity vs. junction source, so the domain_entity shape (ores.refdata.book.org's** Qtdrawer, under its own* C++) is the right template to follow.book.org's** Qtdrawer is the closest worked example of:parent_entity_singular:(the other, unrelated parent-scoping mechanism) – useful for drawer shape (property syntax, table layout for*** Detail fields), not for the new knobs themselves, which have no existing worked example anywhere yet.- Cross-controller wiring in this codebase is centralised in
RefdataPlugin.cpp: every controller is constructed there and handedctx_.main_window=/=ctx_.mdi_area=/=ctx_.client_manager=/etc, then =connectControllerSignals()'d generically, plus bespokeconnect()calls for cross-domain relays (seebookController_->showBookStatusesRequestedbeing relayed to a lookup-list open, right after construction).CalendarDateControllershould be constructed the same way, right aftercalendarController_. - Detail dialogs never hold sibling-controller pointers directly –
they emit signals and the owning controller (or, for cross-domain
cases, the plugin) does the connecting. Confirmed via
CalendarDetailDialog's existingcalendarSaved=/=calendarDeletedsignals, wired up inCalendarController.cpp'sconnect(detailDialog, ...)calls. The existingregenerateAction_=/=onRegenerateClickedpair (added in PR #1707,CalendarDetailDialog.cpp:96-133,500-565) is the exact toolbar-action shape to copy for "Browse Holidays": aQToolBaraction constructed in the dialog's setup, enabled state tied to!createMode_, triggering a handler that (here) just emitsbrowseHolidaysRequested(code())rather than doing NATS work itself (unlikeonRegenerateClicked, which does call NATS directly – browsing needs no request of its own here since the whole point is delegating to the generatedCalendarDateController). EntityController::showListWindow()remains the required zero-arg pure-virtual override;CalendarDateController::openForParent(const QString& calendarCode)(generated) is the real entry point, exactly as designed in1C673C53.
Direction
- Model first: add the
** Qtdrawer tocalendar_date.org, thencompass codegen entity generate calendar_date --address ores.cpp.qtto produce the six new files (ClientCalendarDateModelheader/impl,CalendarDateMdiWindowheader/impl,CalendarDateControllerheader/impl). - Plug the generated controller into
RefdataPlugin(construction +connectControllerSignals), no special wiring needed there beyond what every other controller gets. - Hand-write the
CalendarDetailDialogtoolbar action + signal +CalendarControllerrelay +RefdataPluginconnect, following theregenerateAction_precedent exactly for the toolbar/action half, and theshowBookStatusesRequestedprecedent for the relay half. --diffan unrelated existing entity (e.g.currencyorbook) first to confirm the new knobs, left unset, produce zero drift – same regression-guard pattern every prior task in this chain used.
Notes
Blocked: junction models can't render through ores.cpp.qt yet
Added the ** Qt drawer to calendar_date.org (per the Direction
above) and widened ores.cpp.qt.org's #+model_types: from
domain_entity to domain_entity junction. Verified the routing
layer works correctly:
compass codegen entity generate calendar_date --address ores.cpp.qt --dry-runproduced exactly the right 6 output paths (ClientCalendarDateModel,CalendarDateController,CalendarDateMdiWindow, header+impl each), with thedetail_dialog_*archetypes correctly suppressed via:ores.cpp.qt.detail_dialog_{header,impl,ui}.enabled: falseoverrides.--diffagainstcurrencyandbook(unrelated domain entities) showed zero drift from themodel_typeswidening – the routing change is safe.
But the actual render is broken: class Controller (not
CalendarDateController), #include "", ORES_QT__CONTROLLER_HPP
macro guard. Root cause: the ores.cpp.qt mustache templates and
~1300 lines of enrichment in core.py=/=org_loader.py (deriving
entity_pascal, entity_singular_upper, etc., and parsing the
** Qt drawer's detail_fields=/combo-widget machinery) are
hard-wired to the =domain_entity model shape. A junction model loads
into a structurally different {"junction": {...}} dict
(name_singular instead of entity_singular, no Qt-section parsing
in load_org_junction_model at all, no entity_pascal derivation
anywhere in the junction path) – core.py's generate_qt dispatch
is itself gated on is_domain_entity and 'domain_entity' in data
(core.py ~line 3112), so junction data never even reaches the
enrichment block that would populate these fields.
Deleted the 6 broken generated files rather than leave garbage C++ in
the tree. Left the calendar_date.org Qt drawer and the
model_types widening in place – both are correct, verified-safe
groundwork the split-out task can build on directly.
Split the render-path fix into Codegen: teach the ores.cpp.qt facet to render junction models rather than freehanding a fix to a ~3600-line file under this task's scope – same call B2888D7D made for this exact chain a sprint ago.
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
| Browse Holidays view for a QuantLib calendar | PASSED | All 4 steps passed against TARGET (and a second calendar for parent-scoping); surfaced two pre-existing bugs fixed along the way (see Notes) |
PRs
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | model_types widening on ores.cpp.qt.org is facet-wide – every existing junction (not just calendar_date) becomes a latent candidate for broken Qt generation, not just calendar_date | ores.cpp.qt.org | Accepted | Fixed in follow-up PR #1742: core.py fails fast with a clear error when a junction lacking a ** Qt drawer is targeted at ores.cpp.qt, instead of silently emitting broken output. Verified currency_calendar (an existing Qt-drawer-less junction) now errors cleanly with no files written. |
| 2 | story.org's F081079A row says STARTED but the task's own Status table says BLOCKED; truncated/unlinked ID reference; doubled leading-dash bullets in two new task files | story.org, task_junction-qt-facet-codegen.org, task_prune-merged-branches.org | Accepted | Fixed in follow-up PR #1742: story.org row synced to BLOCKED, ID reference is now a proper org link, both doubled bullets fixed. |
| 3 | calendar_date.org's Columns (Qt model) table missing enum_name column; source's type "text" isn't a recognised type keyword | ores.refdata.calendar_date.org | Accepted | Fixed in follow-up PR #1742: table now has enum_name (Date/IsBusinessDay/Source) and source's type is string. Verified via a real (not just –diff) generation that now compiles and links. |
| 4 | Duplicate "Calendars" menu label – the flat top-level action and the new auxiliary submenu had the identical label/mnemonic within the same Reference Data menu (PR #1749, both reviewers independently flagged) | RefdataPlugin.cpp | Accepted | Renamed the auxiliary submenu to "Calendar Data" to disambiguate. |
| 5 | PR #1749 description said Browse Holidays got Icon::Wand, but the diff actually put Wand on Regenerate and Icon::Table (pre-existing) on Browse Holidays | (description only) | Declined | Wording slip in the PR write-up, not a code issue – code is correct as intended (Wand = "generate/regenerate" semantic on Regenerate; Table = list-view semantic on Browse Holidays). |
| 6 | Inline rationale comments (why synthetic_service/marketdata_service need certain grants) are lost from the generated SQL since the service_registry.org model has no per-prefix comment mechanism | iam_service_db_grants_create.sql | Declined | Accepted, documented trade-off (noted in this task's own Result section) of moving from hand-patched to fully-generated grants; adding a :note: property mechanism is a reasonable future enhancement but out of scope here. |
Result
With the junction-Qt render path (290FBDF6) done and merged,
finished wiring "Browse Holidays" onto Calendar's UI:
RefdataPluginconstructsCalendarDateControlleralongside the other refdata controllers, same constructor-injection pattern ascalendarController_.CalendarDetailDialoggains a "Browse Holidays" toolbar action (same shape as the existingregenerateAction_=/=onRegenerateClickedpair), emitting a newbrowseHolidaysRequested(QString)signal rather than reaching for a sibling controller directly.CalendarControllerrelays that signal outward (mirroringBookController::showBookStatusesRequested's cross-domain relay shape);RefdataPluginconnects it tocalendarDateController_->openForParent(calendarCode).- Reorganised the Reference Data menu's calendar entries per review
during manual testing:
Calendarsstays a flat, alphabetically placed top-level action (was already there); a new, separateCalendarsauxiliary submenu (housingCalendar Rules=/=Calendar Exceptions) sits alongsideCross Rates Matrixat the bottom, matching how other auxiliary/lookup data is grouped. Also flattened theConventionswrapper menu into directCurve Building=/ =Tradingtop-level submenus – neither group was really "a convention" as a category, and the wrapper added a needless click.
Bugs found and fixed during manual QA
Manual testing (doc-run-manual-screenshots-style live verification,
not just --diff) surfaced two real, pre-existing defects unrelated
to this task's own code, both fixed properly rather than patched
around:
refdata_servicemissing a DB grant it has always needed.calendar_materialisation_service(theRegenerateaction, landed in PR #1707) readsores_variability_system_settings_tbldirectly, but the service-registry model (projects/modeling/service_registry.org) never grantedrefdata_serviceSELECTon it –Regeneratefailed withpermission deniedon every environment provisioned from a cleandb recreate. Root-causing this also surfaced that the committediam_service_db_grants_create.sqlhad drifted well ahead of what theservice-registrycodegen profile could actually reproduce (a gap already flagged in capture9CBEE91B):synthetic_service's DML grants,marketdata_service's cross-read ofores_refdata_, and the entire EXECUTE-grants mechanism forpublish_from_dqSECURITY DEFINER functions existed only as hand-added SQL the model/template had no way to express. Rather than hand-patch the live DB (rejected explicitly – SQL files must stay the source of truth) or hand-edit the generated.sqlfile (silently reverted by the next regen), fixed properly: addedExecute prefixessupport toload_org_service_registry_model()(mirroring the existingSelect prefixesmechanism) and to theores.sql.service.db_grantsarchetype template, then reconciledservice_registry.orgto declare every grant the committed SQL already had, plus the newrefdata_service=/=ores_variability_one. Verified byte-for-byte: diffing the full set of_ores_grant_*statements before/after the regen shows exactly one addition (the new grant) and zero removals – every previously-hand-added grant now has a model-driven source.- Two seed rows were needed, not a code bug –
calendar_datesstarts empty on every fresh database (it's materialised only byRegenerate, never by provisioning), which is expected behaviour, not a defect; documented as a scenario step so future testers don't rediscover it.
Verification
--diff against calendar_date's ores.cpp.repository=/
=ores.cpp.service=/=ores.cpp.nats-handler=/=ores.cpp.qt facets all
show zero drift after the service-registry model/template change.
Full local build (compass build --preset linux-clang-debug-make)
green; full ctest run 74/74 passed. Live manual QA via the QA
Validation Runner: all 4 steps of
Browse Holidays view for a QuantLib calendar
passed against a freshly re-provisioned Barclays Plc environment
(db recreate with the corrected grants baked in, no hand-patching).