Bläddra i källkod

Stadsrechten-cascade: analyse, nieuwe endpoint-code en taken 123-126

Onderzocht waarom het Waar-blok van stadsactiviteitAanmaken twee
plaatsselectors naast elkaar toont. Bevindingen:

- De eerste dropdown is een snelkoppeling die alleen createGemeenteID
  vult; de zelf-kies-route heeft nergens een Visibility-conditie.
- field_town_access kan drie niveaus bevatten (provincie/gemeente/plaats).
  plaatsen_bij_gemeente geeft alleen kinderen, dus provincie- en
  plaats-tids leveren [] op -> null-check-crash op de plaats-dropdown.
- Geen enkele dropdown reset de onderliggende, dus createPlaatsID kan een
  plaats uit een eerder gekozen gemeente vasthouden.

CLAUDE.md gecorrigeerd: een stadsactiviteit wordt ALTIJD ongepubliceerd
aangemaakt, ook met stadsrechten (status=0 staat onvoorwaardelijk in
_custom_stadsactiviteit_create). Plus twee blijvende inzichten over de
drie rechtenniveaus en over getJsonField dat bij 0 matches null geeft,
ook met isForList.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 19 timmar sedan
förälder
incheckning
40290944a4
2 ändrade filer med 121 tillägg och 11 borttagningar
  1. 31 11
      CLAUDE.md
  2. 90 0
      TASKS.md

+ 31 - 11
CLAUDE.md

@@ -3264,6 +3264,7 @@ soms een lijst"-verrassingen.** De implementatie staat in
 `flutter_flow_util.dart:349`: 0 matches → `null`, 1 match → `field.first.value`,
 >1 match → `List`. De derde parameter (`isForList`, in de export zichtbaar als
 een losse `true`) forceert altijd een lijst. Praktische gevolgen:
+- ⚠️ **Bij NUL matches geeft hij `null` — óók met `isForList: true`.** De `field.isEmpty`-check staat vóór de `isForList`-tak (`flutter_flow_util.dart:356`), dus `[]` als respons levert géén lege lijst op. Een dropdown die zijn opties uit `(getJsonField(..., true) as List?)!` haalt, crasht daarom op een lege API-respons met `Null check operator used on a null value` — in een release-build een grijs blok zonder melding. Bevestigd 2026-09-20 op de plaats-dropdown van `stadsactiviteitAanmaken`. Gebruik voor lijsten een **Predefined Path** (`$[:]`, Is List) i.p.v. een JSON Path, zie het recept elders in dit bestand.
 - `$[:].veld` op een respons met **één** record levert gewoon een String op —
   daarom werken de zes `launchURL`-knoppen op `HorecagelegenheidCurrent` met
   `$[:].website`. Zodra die view ooit twee rijen per node teruggeeft (het
@@ -5059,17 +5060,22 @@ minder calls per scherm, niet in Views.
 
 ## Domein/architectuurcontext
 
-- **Publicatiegedrag van via de app ingediende content (Bob, 2026-09-18).**
-  Een **evenement** (`evenementCreate`, hangt altijd aan een eigen
-  horecagelegenheid) wordt **direct gepubliceerd** — de indiener is per definitie
-  de eigenaar van die zaak. Een **stadsactiviteit** (`stadsactiviteitCreate`)
-  wordt **alleen direct gepubliceerd voor accounts met stadseditor-rechten**
-  (`field_town_access` op de gemeente, zie de stadsrechten-notitie hieronder);
-  voor alle andere accounts komt hij **ongepubliceerd** binnen en moet iemand hem
-  nakijken. **Praktisch gevolg:** een activiteit die "niet doorkomt" is dus niet
-  automatisch een bug — check eerst de rol van het testaccount vóór je in de
-  create-action of de view gaat zoeken. En elke tekst in de app over "je
-  activiteit staat online" moet die twee gevallen dekken.
+- **Publicatiegedrag van via de app ingediende content.** Een **evenement**
+  (`evenementCreate`, hangt altijd aan een eigen horecagelegenheid) wordt
+  **direct gepubliceerd** (`$node->status = 1`) — de indiener is per definitie de
+  eigenaar van die zaak. Een **stadsactiviteit** (`stadsactiviteitCreate`) komt
+  **ALTIJD ongepubliceerd** binnen — ⚠️ **ook van een account mét
+  stadseditor-rechten** (gecorrigeerd 2026-09-20; hier stond eerder dat
+  stadsrechten direct zouden publiceren, en dat klopt niet).
+  `_custom_stadsactiviteit_create()` zet onvoorwaardelijk `$node->status = 0` en
+  neemt bewust geen `status`-argument aan, zodat een client het nooit kan
+  overschrijven. Er is evenmin een check dat de gekozen plaats binnen je
+  stadsrechten valt: de docblock zegt letterlijk dat `mijn_stadsrechten` **"PUUR
+  INFORMATIEF"** is en **"GEEN toegangscontrole"** — stadsactiviteiten/create
+  staat bewust voor elke ingelogde gebruiker open. **Praktisch gevolg:** een
+  activiteit die "niet online komt" is dus geen bug en geen rollenkwestie, maar
+  wacht gewoon op de redactie; de snackbar ("wordt beoordeeld door de redactie")
+  is voor iedereen correct.
 
 - **Geen user-specifieke elementen op overzichtspagina's — besluit Bob,
   2026-09-13.** Concreet: géén favorietenhartje op de kaarten van een
@@ -5344,6 +5350,20 @@ de inlognaam van de auteur, dus user enumeration op al je redacteuren.
   e.d.) zijn een **bewuste kopie** van `PUitgaanSliderComponent`/
   `UitgaantabelKaartComponent` + een eigen cityid-loze API-call — geen
   dode code, niet meenemen in opschoonacties.
+- **⚠️ `field_town_access` kan een term op ELK van de drie niveaus bevatten —
+  ga er nooit van uit dat het een gemeente is.** Gemeten 2026-09-20 op productie:
+  van drie accounts met stadsrechten had één 4× gemeente, hadden twee nog
+  plaats-tids uit de tijd vóór 2026-08-27, en na een herstelronde stonden er
+  **provincie**-tids in (de `term_reference_tree`-widget staat op Max depth 2 en
+  kent geen minimum, dus provincie is gewoon aanvinkbaar — en Bob geeft rechten
+  bewust op provincieniveau omdat dat veel werk scheelt). Bepaal het niveau met
+  `count(taxonomy_get_parents_all($tid))`: **1 = provincie, 2 = gemeente,
+  3 = plaats** (die functie levert de term zélf als eerste element). Let op de
+  gevolgen aan de app-kant: **`plaatsen_bij_gemeente.json` geeft alleen directe
+  KINDEREN**, dus een provincie-tid levert `[]` (kinderen zijn gemeenten) en een
+  plaats-tid ook. `mijn_stadsrechten` geeft daarom sinds 2026-09-20 per recht de
+  volledige keten mee (`niveau`, `provincie_tid`, `gemeente_tid`, `plaats_tid`,
+  `keten`) zodat de app zijn cascade kan voorvullen tot zover het recht reikt.
 - **City-editor-stadsrechten (`field_town_access`) zijn sinds 2026-08-27
   gemeente-niveau, niet meer plaats-niveau — taxonomie zelf (Provincie→
   Gemeente→Plaats, vid 5) is en blijft 3 niveaus, alleen de rechten-laag

+ 90 - 0
TASKS.md

@@ -51,6 +51,8 @@ Google Play en één doorloop die alleen Bob kan doen.
 | ~~117~~ | ~~Kalenderpermissie eruit~~ | ✅ **AF** (Bob 2026-09-20, export-geverifieerd): `READ_CALENDAR`/`WRITE_CALENDAR` én `NSCalendarsUsageDescription` zijn weg; `CAMERA` staat er nog (terecht) |
 | **118** | AdMob EU-consentbericht | ⚠️ Half: de FlutterFlow-toggle staat goed, maar het bericht moet in de **AdMob-console** gepubliceerd zijn. Eigen blok hieronder |
 | **122** | Teksten aanmaakpagina's doornemen | Labels, hints en Engelse vertalingen op de activiteiten- en evenementenpagina. Inventaris mét i18n-sleutels staat in een eigen blok hieronder; o.a. "Categorie **evenement**" als kop op de *activiteiten*pagina, "Selectee...", "WebsiteURL"/"TicketsURL" en een sterretje dat niet klopt met de introtekst |
+| **123** | `mijn_stadsrechten` uitbreiden met de keten | **Code staat klaar en is op productie getest.** Vervang regels 316-372 van `custom.evenementen_aanmaken.inc` + `cc all`. Blokkeert 124 |
+| **124-126** | Stadsrechten-cascade op `stadsactiviteitAanmaken` | Voorvullen van provincie/gemeente/plaats (124), een **crash** bij een lege plaatsenlijst (125) en een stille verkeerde plaats bij het wisselen van gemeente (126). Eigen blok hieronder |
 
 ---
 #
@@ -429,6 +431,94 @@ waarde.
 geneste dialogen. Zeg het even als Claude die mag oppakken; B t/m D zijn
 schrijfkeuzes en horen bij jou.
 
+### 123 · Stadsrechten-cascade op `stadsactiviteitAanmaken` — Drupal-deel · Eigenaar: Bob
+
+**Code staat klaar en is getest** (2026-09-20, Claude): vervang in
+`sites/all/modules/custom/custom.evenementen_aanmaken.inc` de **regels 316 t/m
+372** (docblock t/m sluitende accolade van `_custom_mijn_stadsrechten_service()`)
+door de aangeleverde versie. Daarna `drush @<alias> cc all`.
+
+**Waarom:** `field_town_access` mag een term op **elk** van de drie niveaus van
+de town-vocabulary bevatten — plaats (tot 2026-08-27), gemeente (huidige
+standaard) of provincie (de widget staat op Max depth 2 en laat dat toe; Bob
+geeft ze bewust op provincieniveau, dat scheelt veel werk). De app stopte die tid
+rechtstreeks in `plaatsen_bij_gemeente`, en dat geeft bij provincie én plaats een
+lege lijst → zie taak 125.
+
+De nieuwe versie geeft per recht de **volledig uitgeklapte keten** terug:
+`niveau` · `provincie_tid`/`_titel` · `gemeente_tid`/`_titel` ·
+`plaats_tid`/`_titel` · `keten` (`"provincie|gemeente|plaats"`, ontbrekende delen
+leeg) · `label` (bv. *"Noord-Brabant (hele provincie)"*). `tid`, `titel`,
+`parent_tid` en `parent_titel` blijven ongewijzigd staan, dus bestaande
+consumenten merken niets. Sortering is hiërarchisch i.p.v. alfabetisch op titel.
+
+Getest op productie onder een testnaam, alle drie de niveaus:
+
+| account | recht | `keten` |
+|---|---|---|
+| bobcity | 28694 Amsterdam (gemeente) | `28666\|28694\|` |
+| bobhoreca | 27409 Noord-Brabant (provincie) | `27409\|\|` |
+| Team Klein Frankrijk | 29912 Zuid-Holland (provincie) | `29912\|\|` |
+
+### 124 · Stadsrechten-cascade — app-deel · Eigenaar: nader te bepalen (na 123)
+
+De eerste dropdown in het *Waar*-blok van `stadsactiviteitAanmaken` moet de drie
+cascade-dropdowns voorvullen tot zover het recht reikt, in plaats van alleen
+`createGemeenteID` te zetten:
+
+| recht | voorvullen | gebruiker kiest nog |
+|---|---|---|
+| provincie | provincie | gemeente + plaats |
+| gemeente | provincie + gemeente | plaats |
+| plaats (oud) | alle drie | niets |
+
+**Stappen:** dropdown-waarde omzetten van `$[:].tid` naar **`$[:].keten`** (label
+op `$[:].label`), plus drie custom functions van **één** String-argument
+(`ketenProvincie` / `ketenGemeente` / `ketenPlaats` → `split('|')[n]`). Die ene
+parameter is bewust: een lookup op de hele respons vraagt een custom function met
+meerdere argumenten, en die binding loopt in de Action Flow Editor structureel
+vast (zie `CLAUDE.md`). On Selected → `Update Page State` voor
+`createProvincieID`/`createGemeenteID`/`createPlaatsID` + `Set Form Field` op de
+drie dropdowns.
+
+Een lege `Set Form Field`-waarde wordt geweigerd ("Value cannot be empty"), dus
+voor de lege delen van de keten is **Reset Form Fields** nodig — wat meteen de
+tweede helft van taak 126 afhandelt.
+
+**Voorvullen, niet verbergen** (besluit Bob 2026-09-20): met verbergen kun je
+achteraf niet meer wisselen zonder eerst de rechten-dropdown leeg te maken.
+
+### 125 · Crash: lege plaatsenlijst sloopt de Plaats-dropdown · Eigenaar: nader te bepalen
+
+`stadsactiviteit_aanmaken_widget.dart:1878` doet
+`(getJsonField(respons, r'$[:].plaatsid', true) as List?)!`. `getJsonField` geeft
+bij **0 matches `null`** — ook met `isForList: true`, want de `isEmpty`-check
+staat ervóór ([`flutter_flow_util.dart:356`]). Een lege respons `[]` geeft dus
+`Null check operator used on a null value`; in een release-build een grijs blok
+zonder melding.
+
+Treedt nu op bij elke provincie- of plaats-tid in `field_town_access`, maar staat
+**los van taak 123/124**: ook een gemeente zónder plaatsen in de taxonomie geeft
+dezelfde crash. Zelfde patroon staat op de **gemeente**- en **provincie**-dropdown
+van diezelfde pagina — alle drie langslopen.
+
+Fix: Generate-Dynamic-Children-achtige null-veiligheid via een **Predefined Path**
+op de API-call (`$[:]`, Is List) i.p.v. een JSON Path — dat genereert
+`(XCall.items(jsonBody)?.toList() ?? [])`. Recept staat in `CLAUDE.md`.
+
+### 126 · Stale `createPlaatsID` bij het wisselen van gemeente · Eigenaar: nader te bepalen
+
+Geen van de vier dropdowns in het *Waar*-blok reset de onderliggende. Scenario:
+kies gemeente A → plaats A₁ (`createPlaatsID` gezet) → wissel naar gemeente B.
+De Plaats-dropdown toont dan weer zijn hint (`FlutterFlowDropDown` filtert een
+waarde die niet in de opties zit weg, [`flutter_flow_drop_down.dart:101`]), maar
+`createPlaatsID` houdt **A₁** vast. De verzendknop blijft dus zichtbaar en je
+dient in voor de vorige plaats. Stil, geen foutmelding.
+
+Fix: `Reset Form Fields` + `Update Page State` (Reset Value) op de onderliggende
+velden in de On-Selected van provincie, gemeente én de rechten-dropdown. Overlapt
+met taak 124 — samen oppakken.
+
 ### 121 · 15 verweesde mappen in `lib/` · Eigenaar: Bob (Claude verwijdert niets)
 
 Gevonden 2026-09-20 bij het committen van de achterstallige working tree. Deze