@atlashub/smartstack-cli 3.45.0 → 3.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -229,6 +229,72 @@ public class {Name} : BaseEntity, IScopedTenantEntity, IAuditableEntity
229
229
  }
230
230
  ```
231
231
 
232
+ ### Entity Pattern — Person Extension (User-linked roles)
233
+
234
+ For entities representing a "person role" of a User (Employee, Customer, Contact). Avoids duplicating User personal data.
235
+
236
+ > **Full reference:** See `references/person-extension-pattern.md` for complete entity, config, service, DTO, and frontend patterns.
237
+
238
+ **Two variants:**
239
+
240
+ | Variant | UserId | Use case |
241
+ |---------|--------|----------|
242
+ | **Mandatory** | `Guid UserId` | Employee, Manager — always a User |
243
+ | **Optional** | `Guid? UserId` | Customer, Contact — may or may not have account |
244
+
245
+ **Mandatory variant (entity):**
246
+ ```csharp
247
+ public class Employee : BaseEntity, ITenantEntity, IAuditableEntity
248
+ {
249
+ public Guid TenantId { get; private set; }
250
+ public string? CreatedBy { get; set; }
251
+ public string? UpdatedBy { get; set; }
252
+
253
+ // Person Extension — mandatory UserId
254
+ public Guid UserId { get; private set; }
255
+ public User? User { get; private set; }
256
+
257
+ // Business properties ONLY — ZERO person fields (FirstName, LastName, Email come from User)
258
+ public string Code { get; private set; } = null!;
259
+ public DateOnly HireDate { get; private set; }
260
+ }
261
+ ```
262
+
263
+ **Optional variant (entity):**
264
+ ```csharp
265
+ public class Customer : BaseEntity, ITenantEntity, IAuditableEntity
266
+ {
267
+ public Guid TenantId { get; private set; }
268
+ public string? CreatedBy { get; set; }
269
+ public string? UpdatedBy { get; set; }
270
+
271
+ // Person Extension — optional UserId
272
+ public Guid? UserId { get; private set; }
273
+ public User? User { get; private set; }
274
+
275
+ // Person fields (standalone when no User linked)
276
+ public string FirstName { get; private set; } = null!;
277
+ public string LastName { get; private set; } = null!;
278
+ public string? Email { get; private set; }
279
+
280
+ // Computed — resolve from User if linked, else own fields
281
+ public string DisplayFirstName => User?.FirstName ?? FirstName;
282
+ public string DisplayLastName => User?.LastName ?? LastName;
283
+ public string? DisplayEmail => User?.Email ?? Email;
284
+ }
285
+ ```
286
+
287
+ **EF Config (mandatory):** `builder.HasIndex(e => new { e.TenantId, e.UserId }).IsUnique();`
288
+ **EF Config (optional):** `builder.HasIndex(e => new { e.TenantId, e.UserId }).IsUnique().HasFilter("[UserId] IS NOT NULL");`
289
+
290
+ **Service rules (both):** `Include(x => x.User)` on all queries. Inject `ICoreDbContext` for User reads. CreateAsync verifies User exists + no duplicate `(TenantId, UserId)`.
291
+
292
+ **DTO rules:** Mandatory CreateDto has `Guid UserId` + business fields only. Optional CreateDto has `Guid? UserId` + person fields. ResponseDto (both) includes `Display*` fields.
293
+
294
+ **FORBIDDEN (mandatory variant):** `FirstName`, `LastName`, `Email`, `PhoneNumber` properties in entity — these come from User.
295
+
296
+ ---
297
+
232
298
  ### MCP tenantMode Parameter
233
299
 
234
300
  When calling `scaffold_extension`, use the `tenantMode` parameter:
@@ -738,6 +804,9 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
738
804
  | `IEmployeeService.cs` in Infrastructure | Interface belongs in Application — move to `Application/Interfaces/` |
739
805
  | Service implementation in Application layer | Implementations belong in Infrastructure — Application only contains interfaces |
740
806
  | Controller injects `IRepository<T>` directly | Controllers use Application services, not repositories — add a service layer |
807
+ | `FirstName`/`LastName`/`Email` on mandatory person extension entity | These come from User — use `Display*` computed properties from the User join |
808
+ | Missing `Include(x => x.User)` on person extension service | Person extension entities MUST always include User for display fields |
809
+ | Missing unique index on `(TenantId, UserId)` for person extension | One person-role per User per Tenant — add unique constraint in EF config |
741
810
 
742
811
  ---
743
812
 
@@ -59,6 +59,11 @@ Web → Application (via API clients)
59
59
  - No Code/IsDeleted/RowVersion in BaseEntity — add business properties yourself
60
60
  - Domain events for state changes
61
61
  - Value objects for composite values
62
+ - **Person Extension Pattern:** If entity has `personRoleConfig` (from PRD or feature.json):
63
+ - Load `references/person-extension-pattern.md` for full patterns
64
+ - **Mandatory** (`userLinkMode: 'mandatory'`): `Guid UserId` (non-nullable), ZERO person fields (`FirstName`, `LastName`, `Email`), unique index `(TenantId, UserId)`
65
+ - **Optional** (`userLinkMode: 'optional'`): `Guid? UserId` (nullable), own person fields + computed `Display*` properties, filtered unique index `(TenantId, UserId) WHERE UserId IS NOT NULL`
66
+ - EF Config: `builder.Ignore()` on computed `Display*` properties (optional variant only)
62
67
 
63
68
  ---
64
69
 
@@ -119,6 +124,13 @@ Web → Application (via API clients)
119
124
  - DTOs separate from domain entities
120
125
  - Service interfaces in Application, implementations in Infrastructure
121
126
  - **FORBIDDEN:** `tenantId: Guid.Empty`, `TenantId!.Value`, queries without TenantId filter, `ICurrentUser` (does not exist — use `ICurrentUserService` + `ICurrentTenantService`), `string` type for date fields
127
+ - **Person Extension services:** If entity has `personRoleConfig`:
128
+ - `Include(x => x.User)` on ALL queries (mandatory)
129
+ - Inject `ICoreDbContext` for User existence checks
130
+ - Mandatory: search on `User.FirstName`, `User.LastName`, `User.Email`
131
+ - Optional: search fallback `User.FirstName ?? x.FirstName`
132
+ - CreateAsync: verify User exists + no duplicate `(TenantId, UserId)`
133
+ - ResponseDto: include `Display*` fields resolved from User join
122
134
 
123
135
  ---
124
136
 
@@ -151,6 +163,7 @@ Web → Application (via API clients)
151
163
  - NEVER use `[Authorize]` without specific permission
152
164
  - Swagger XML documentation
153
165
  - Return DTOs, never domain entities
166
+ - **Person Extension DTOs:** ResponseDto MUST include `Display*` fields (`DisplayFirstName`, `DisplayLastName`, `DisplayEmail`) resolved from User join. Mandatory CreateDto: `Guid UserId` only. Optional CreateDto: `Guid? UserId` + person fields.
154
167
 
155
168
  ---
156
169
 
@@ -75,6 +75,13 @@ IF delegate_mode:
75
75
  Extract entities from domain tasks:
76
76
  {entities} = prd.tasks.filter(t => t.category == 'domain').map(t => entity name from t.description)
77
77
 
78
+ Extract code patterns from feature.json (if available):
79
+ IF {feature_path} exists:
80
+ Read feature.json → for each entity with codePattern defined:
81
+ {code_patterns}[] += { entity, strategy, prefix, digits, includeTenantSlug, separator }
82
+ ELSE:
83
+ {code_patterns} = [] # will use heuristic fallback in analysis-methods.md
84
+
78
85
  Extract complexity:
79
86
  {module_complexity} = heuristic from task count:
80
87
  <= 10 tasks → "simple-crud"
@@ -133,7 +140,7 @@ IF failure → warn user, continue in degraded mode (manual tools only)
133
140
 
134
141
  > **Reference:** Load `references/challenge-questions.md` for hierarchy rules and challenge questions:
135
142
  > - 4-level hierarchy structure (Application → Module → Section → Resource)
136
- > - Challenge questions (4a: Application, 4b: Module, 4c: Sections, 5a: Entities, 5b: Dependencies)
143
+ > - Challenge questions (4a: Application, 4b: Module, 4c: Sections, 5a: Entities, 5b: Dependencies, 5c: Code Generation)
137
144
  > - Validation rules (sections MUST have at least one entry)
138
145
  > - Storage format for each level
139
146
  > - Delegate mode skip (if -d)
@@ -169,23 +176,37 @@ Load and execute entity questions from `references/challenge-questions.md` secti
169
176
 
170
177
  Load and execute dependency questions from `references/challenge-questions.md` section 5b.
171
178
 
172
- ### 5c. Store Responses
179
+ ### 5c. Code Generation Strategy
180
+
181
+ Load and execute code generation questions from `references/challenge-questions.md` section 5c.
182
+
183
+ **Skip if:**
184
+ - `delegate_mode` is true (PRD defines code patterns)
185
+ - `foundation_mode` is true (code strategy comes later)
186
+ - `feature.json` exists AND all entities in `{entities}` have `codePattern` defined
187
+
188
+ **If feature.json covers SOME but not all entities:**
189
+ → Only ask for entities without `codePattern` in feature.json
190
+ → Merge feature.json patterns + user answers into `{code_patterns}`
191
+
192
+ ### 5d. Store Responses
173
193
 
174
194
  ```yaml
175
195
  {entities}: string[] # Main entities to manage
176
196
  {module_complexity}: string # "simple-crud" | "crud-rules" | "crud-workflow" | "complex"
177
197
  {has_dependencies}: string # "none" | "references" | "unknown"
178
198
  {key_properties}: string[] # Key business properties mentioned by user
199
+ {code_patterns}: object[] # Per-entity code generation config (from 5c or feature.json)
179
200
  ```
180
201
 
181
202
  These values are propagated to:
182
- - **step-01 (Analyze):** Guide code exploration focus
203
+ - **step-01 (Analyze):** Guide code exploration focus + code pattern detection (uses `{code_patterns}` as Priority 1)
183
204
  - **step-02 (Plan):** Inform layer mapping (e.g., workflow → needs /workflow skill)
184
- - **step-03 (Execute):** Entity scaffolding with correct properties and relationships
205
+ - **step-03 (Execute):** Entity scaffolding with correct properties, relationships, and code generation strategy
185
206
 
186
207
  ---
187
208
 
188
- ## 5d. Scope Complexity Guard (BLOCKING)
209
+ ## 5e. Scope Complexity Guard (BLOCKING)
189
210
 
190
211
  > **Root cause (test-apex-007):** `/apex` was invoked with 3 applications and 7 entities.
191
212
  > The model's context window saturated, causing: incomplete migrations, lost conventions,
@@ -295,6 +316,7 @@ APEX INIT COMPLETE — v{{SMARTSTACK_VERSION}}
295
316
  Task: {task_description}
296
317
  Hierarchy: {app_name} → {module_code} → {sections[].code}
297
318
  Entities: {entities} | Complexity: {module_complexity} | Deps: {has_dependencies}
319
+ Code: {code_patterns summary — e.g., "Employee:sequential(emp), Contract:year-seq(ctr)" or "all manual" or "none"}
298
320
  PRD: {prd_path||none} | Feature: {feature_path||none} | Flags: {active_flags}
299
321
  MCP: {available|degraded} | Needs: migration/seed/workflow/notification = {yes|no}
300
322
  NEXT STEP: step-01-analyze
@@ -55,6 +55,19 @@ For each entity to create/modify:
55
55
  → Verify entity matches patterns in references/smartstack-api.md
56
56
  ```
57
57
 
58
+ ### Person Extension Detection
59
+
60
+ ```
61
+ IF entity has personRoleConfig (from PRD or feature.json):
62
+ → LOAD references/person-extension-pattern.md
63
+ → Apply variant: mandatory or optional (from personRoleConfig.userLinkMode)
64
+ → scaffold_extension with options: { isPersonRole: true, userLinkMode: '{variant}' }
65
+ → VERIFY: UserId FK present, no duplicate person fields (mandatory)
66
+ → VERIFY: EF config has unique index on (TenantId, UserId)
67
+ → VERIFY (mandatory): entity has ZERO person fields (FirstName, LastName, Email, PhoneNumber)
68
+ → VERIFY (optional): entity has own person fields + computed Display* properties
69
+ ```
70
+
58
71
  ### EF Core Configurations
59
72
 
60
73
  ```
@@ -26,6 +26,7 @@
26
26
  | `SystemEntity` | - | `Id` (Guid), `CreatedAt` (pas de TenantId) | Entites systeme (navigation, permissions) |
27
27
  | `BaseEntity` | `ISoftDeletable` | + `IsDeleted`, `DeletedBy`, `DeletedAt` | Entite avec suppression logique |
28
28
  | `HierarchicalEntity` | - | + `ParentId`, `Level`, `Path` | Arborescences (categories, menus) |
29
+ | `BaseEntity + UserId FK` | `ITenantEntity` | + `UserId` (Guid FK to User) | Person Role Extension — entity represents a person linked to User. Use `isPersonRole: true` in scaffold_extension. |
29
30
 
30
31
  > **IMPORTANT :** Ne JAMAIS proposer `CreatedBy`, `UpdatedBy`, `IsDeleted`, `TenantId` comme champs explicites — ils sont AUTOMATIQUES via la classe de base ou l'interface.
31
32
 
@@ -6,6 +6,28 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## 0. Pre-check: Person Extension
10
+
11
+ **BEFORE** running the entity architecture scoring, check if this entity represents a person role.
12
+
13
+ ### Detection criteria:
14
+ 1. **Name match**: Entity name is in the known person role list (Employee, Customer, Manager, Supplier, etc.)
15
+ 2. **Attribute match**: Entity has 3+ attributes among (firstName, lastName, email, phoneNumber, department, jobTitle, address)
16
+
17
+ ### If detected:
18
+ → Recommend **Person Extension Pattern** (link to User via UserId FK)
19
+ → Ask user to confirm variant: mandatory (always a User) or optional (may or may not have a User)
20
+ → Store `personRoleConfig` in entity structure card
21
+ → **Skip standard entity architecture scoring** for person-specific fields (they come from User)
22
+
23
+ ### Known person role names:
24
+ | Variant | Entity names |
25
+ |---------|-------------|
26
+ | **Mandatory** (always a User) | Employee, Manager, Instructor, Teacher, Trainer, Technician |
27
+ | **Optional** (may have a User) | Customer, Client, Supplier, Vendor, Patient, Student, Member, Participant, Candidate, Applicant, Contact, Lead, Prospect, Volunteer, Contractor, Freelancer, Consultant |
28
+
29
+ ---
30
+
9
31
  ## 1. Entity Scoring Grid (5 criteria, 0-3 each)
10
32
 
11
33
  For EACH referenced entity detected during approfondissement, score silently:
@@ -34,6 +34,20 @@ For each BR include:
34
34
 
35
35
  Layers: Domain, Application, Infrastructure, API, Frontend
36
36
 
37
+ ## Person Extension Mapping
38
+
39
+ When an entity has `personRoleConfig` in its analysis data, it MUST be carried into the PRD:
40
+
41
+ | Source (feature.json) | Target (PRD) |
42
+ |----------------------|--------------|
43
+ | `analysis.entities[].personRoleConfig` | `modules[].entities[].personRoleConfig` |
44
+ | `personRoleConfig.userLinkMode` | Determines scaffold options: `isPersonRole: true, userLinkMode: "{mode}"` |
45
+ | `personRoleConfig.inheritedFields` | Fields to exclude from entity (mandatory) or mark as fallback (optional) |
46
+
47
+ The `personRoleConfig` object is passed through verbatim to ensure `/apex` and `/ralph-loop` receive the person extension configuration.
48
+
49
+ ---
50
+
37
51
  ## API Endpoint Summary
38
52
 
39
53
  > **ABSOLUTE RULE:** Copy **EXACTLY** from `specification.apiEndpoints[]`. **NEVER** reinvent routes.
@@ -24,6 +24,7 @@
24
24
  | string (multiline) | TextArea | entity.required | rows: 4 |
25
25
  | enum | Select | entity.required | source: enum name |
26
26
  | FK:Entity | EntityLookup | entity.required | source: target entity, searchable. Component: `@/components/ui/EntityLookup`. NEVER plain text input for FK Guid fields. Backend API MUST support `?search=` param. See `smartstack-frontend.md` section 6. |
27
+ | FK:User (person role) | `EntityLookup` | entity.required | searchable dropdown searching Users by name + email. `<EntityLookup apiEndpoint="/api/administration/users" mapOption={(user) => ({id: user.id, label: `${user.firstName} ${user.lastName}`, sublabel: user.email})} />` |
27
28
  | decimal | NumberInput | entity.required | — |
28
29
  | int | NumberInput | entity.required | — |
29
30
  | datetime | DatePicker | entity.required | — |
@@ -143,6 +143,55 @@ If team agent: use heuristic defaults autonomously.
143
143
 
144
144
  **Reference:** See `apex/references/code-generation.md` for full ICodeGenerator<T> implementation details, DI registration patterns, and backend service integration.
145
145
 
146
+ #### 6b-ter. Person Role Detection
147
+
148
+ > **CRITICAL: Detect entities representing person roles to avoid duplicating User data.**
149
+ > SmartStack User entity already contains: FirstName, LastName, Email, DisplayName, DepartmentId, JobTitleId, PhoneNumber, etc.
150
+ > Entities that represent person roles MUST be linked to User via the Person Extension Pattern.
151
+
152
+ **Step 1 — Name-based detection:**
153
+
154
+ Known person role names (mandatory User link):
155
+ - Employee, Manager, Instructor, Teacher, Trainer, Technician
156
+
157
+ Known person role names (optional User link):
158
+ - Customer, Client, Supplier, Vendor, Patient, Student, Member, Participant, Candidate, Applicant, Contact, Lead, Prospect, Volunteer, Contractor, Freelancer, Consultant
159
+
160
+ **Step 2 — Attribute-based detection:**
161
+
162
+ If an entity has 3+ attributes among: firstName, lastName, email, phoneNumber, department, jobTitle, address → flag as potential person role.
163
+
164
+ **Step 3 — Interactive confirmation:**
165
+
166
+ IF entity name matches a known person role OR attribute detection triggers:
167
+
168
+ Use `AskUserQuestion` with 3 options:
169
+ 1. "User link mandatory (Recommended)" — the entity is ALWAYS a system User (Employee, Manager)
170
+ 2. "User link optional" — the entity MAY have a system account (Customer with portal)
171
+ 3. "Independent entity" — no User link (rare: only if the person will NEVER log in)
172
+
173
+ **Step 4 — Apply Person Extension config:**
174
+
175
+ IF option 1 or 2 selected:
176
+ - Store `personRoleConfig` in the entity's structure card
177
+ - For mandatory mode: REMOVE attributes that duplicate User (firstName, lastName, email, displayName, department, jobTitle, phoneNumber) from entity attributes
178
+ - For optional mode: KEEP person attributes but mark as "fallback when no User linked"
179
+
180
+ > **STRUCTURE CARD: analysis.entities[].personRoleConfig**
181
+ > ```json
182
+ > {
183
+ > "personRoleConfig": {
184
+ > "linkedEntity": "User",
185
+ > "fkField": "UserId",
186
+ > "userLinkMode": "mandatory|optional",
187
+ > "inheritedFields": ["FirstName", "LastName", "Email", "DisplayName",
188
+ > "Department", "JobTitle", "PhoneNumber"]
189
+ > }
190
+ > }
191
+ > ```
192
+
193
+ IF option 3 selected: do not add personRoleConfig (entity stays independent).
194
+
146
195
  #### 6c. Process Flow
147
196
 
148
197
  Define the main business process flow for this module (not lifecycle — that's in 8j):
@@ -236,7 +236,7 @@ read_gitflow_config() {
236
236
  GF_RELEASE_PREFIX=${GF_RELEASE_PREFIX:-release/}
237
237
  GF_HOTFIX_PREFIX=${GF_HOTFIX_PREFIX:-hotfix/}
238
238
  GF_TAG_PREFIX=${GF_TAG_PREFIX:-v}
239
- GF_VERSION=${GF_VERSION:-0.1.0}
239
+ GF_VERSION=${GF_VERSION:-0.0.0}
240
240
 
241
241
  # Translate all stored paths to current platform format
242
242
  # This ensures WSL paths are /mnt/d/... and Windows paths use forward slashes
@@ -551,7 +551,7 @@ GitFlow configuration JSON template v2.1.0 (aligned with `templates/config.json`
551
551
  },
552
552
  "versioning": {
553
553
  "strategy": "semver",
554
- "current": "0.1.0",
554
+ "current": "0.0.0",
555
555
  "tagPrefix": "v",
556
556
  "sources": ["csproj", "package.json", "VERSION"]
557
557
  },
@@ -13,7 +13,7 @@ VERSION=$(grep -oP '<Version>\K[^<]+' "$DEVELOP_FULL_PATH"/*.csproj 2>/dev/null
13
13
  [ -z "$VERSION" ] && VERSION=$(grep -oP '"version":\s*"\K[^"]+' "$DEVELOP_FULL_PATH/package.json" 2>/dev/null)
14
14
  [ -z "$VERSION" ] && VERSION=$(cat "$DEVELOP_FULL_PATH/VERSION" 2>/dev/null)
15
15
  [ -z "$VERSION" ] && VERSION=$(git -C "$DEVELOP_FULL_PATH" describe --tags --abbrev=0 2>/dev/null | sed 's/^v//')
16
- [ -z "$VERSION" ] && VERSION="0.1.0"
16
+ [ -z "$VERSION" ] && VERSION="0.0.0"
17
17
  ```
18
18
 
19
19
  ## Version will be stored in config.json
@@ -77,9 +77,20 @@ See [references/init-structure-creation.md](../references/init-structure-creatio
77
77
  - Worktree creation (organized and simple modes)
78
78
  - GitFlow config directory creation
79
79
 
80
- ### 9. Create Configuration
80
+ ### 9. Detect Version
81
81
 
82
- **Write `.claude/gitflow/config.json` in develop worktree.**
82
+ **⛔ MUST run BEFORE creating config.json (step 10) — the detected `{VERSION}` is needed in the config template.**
83
+
84
+ See [references/init-version-detection.md](../references/init-version-detection.md) for:
85
+ - Version detection with priority (csproj, package.json, tags, etc.)
86
+ - Platform-agnostic path handling
87
+ - Fallback: `0.0.0` if no version source found
88
+
89
+ **⛔ WARNING:** Do NOT confuse the config schema version (`"version": "2.1.0"` at line 1 of config.json) with the project version (`"versioning.current": "{VERSION}"`). They are completely different values.
90
+
91
+ ### 10. Create Configuration
92
+
93
+ **Write `.claude/gitflow/config.json` in develop worktree using the `{VERSION}` detected in step 9.**
83
94
 
84
95
  See [references/init-config-template.md](../references/init-config-template.md) for:
85
96
  - Full config.json v2.1.0 template (platform, workspace, repository, git, worktrees, versioning, efcore, workflow, language)
@@ -87,12 +98,6 @@ See [references/init-config-template.md](../references/init-config-template.md)
87
98
 
88
99
  **⛔ IMPORTANT:** Store paths using Windows-style format (`D:/path/to/folder`). The `read_gitflow_config()` in `_shared.md` translates to platform format at read time.
89
100
 
90
- ### 10. Detect Version
91
-
92
- See [references/init-version-detection.md](../references/init-version-detection.md) for:
93
- - Version detection with priority (csproj, package.json, tags, etc.)
94
- - Platform-agnostic path handling
95
-
96
101
  ### 10b. Post-Init Validation
97
102
 
98
103
  **⛔ MANDATORY: Verify the structure was created correctly.**
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "versioning": {
49
49
  "strategy": "semver",
50
- "current": "0.1.0",
50
+ "current": "0.0.0",
51
51
  "tagPrefix": "v",
52
52
  "sources": ["csproj", "package.json", "VERSION"]
53
53
  },
@@ -35,6 +35,19 @@ Tasks MUST be executed in this order. Each category depends on the previous ones
35
35
 
36
36
  ---
37
37
 
38
+ ## Person Role Entities
39
+
40
+ Entities with `personRoleConfig` (Person Extension Pattern) have a special architectural requirement:
41
+
42
+ - **ICoreDbContext access required**: Person Extension entities need to read User data from the core schema
43
+ - This is an **exception** to the normal pattern where extension entities only use `IExtensionsDbContext`
44
+ - Service must inject BOTH `IExtensionsDbContext` (for the entity) AND `ICoreDbContext` (for User reads)
45
+ - Service must `Include(x => x.User)` on all queries
46
+ - For mandatory mode: search on `User.FirstName`, `User.LastName`, `User.Email`
47
+ - For optional mode: search with fallback (`User?.FirstName ?? x.FirstName`)
48
+
49
+ ---
50
+
38
51
  ## Batch Grouping Rules
39
52
 
40
53
  When selecting tasks for a batch: