Skip to content

Commit 7f0f75d

Browse files
Merge branch 'dev' into user/ariana/LEGLINK-961-MeasureEval-handleFhirResourceswithIdenticalds
2 parents 7410dd7 + 0aa808c commit 7f0f75d

5 files changed

Lines changed: 78 additions & 13 deletions

File tree

DotNet/Automation.UI/Controllers/OrganizationResourceMapsController.cs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Automation.UI.Models;
22
using Automation.UI.Services.Persistence;
33
using Microsoft.AspNetCore.Mvc;
4+
using MongoDB.Driver;
45

56
namespace Automation.UI.Controllers;
67

@@ -23,22 +24,40 @@ public async Task<IActionResult> GetJson(Guid id, CancellationToken ct)
2324

2425
[HttpPost]
2526
[ValidateAntiForgeryToken]
26-
public async Task<IActionResult> SaveInline([FromBody] OrganizationResourceMapTemplate model, CancellationToken ct)
27+
public async Task<IActionResult> SaveInline(
28+
[FromBody] OrganizationResourceMapTemplate model,
29+
CancellationToken ct)
2730
{
2831
if (string.IsNullOrWhiteSpace(model.Name))
2932
return BadRequest("Template name is required.");
33+
3034
if (model.Conditions == null || model.Conditions.Count == 0)
3135
return BadRequest("At least one mapping condition is required.");
36+
3237
if (model.Conditions.Any(c => string.IsNullOrWhiteSpace(c.FhirPath)))
3338
return BadRequest("All mapping conditions must include a FHIRPath.");
3439

40+
model.Name = model.Name.Trim();
41+
42+
var templates = await store.GetAllAsync(ct);
43+
44+
if (HasDuplicateName(templates, model.Name, model.Id))
45+
{
46+
return Conflict(
47+
$"An Organization Resource Map named '{model.Name}' already exists.");
48+
}
49+
3550
var existing = await store.GetByIdAsync(model.Id, ct);
51+
3652
if (existing is { IsSystem: true })
37-
return StatusCode(StatusCodes.Status403Forbidden, "System template cannot be modified.");
53+
return StatusCode(
54+
StatusCodes.Status403Forbidden,
55+
"System template cannot be modified.");
3856

3957
model.IsSystem = false;
4058
model.IsDefault = existing?.IsDefault ?? model.IsDefault;
4159
model.UpdatedAt = DateTimeOffset.UtcNow;
60+
4261
await store.UpsertAsync(model, ct);
4362
return Json(new { id = model.Id });
4463
}
@@ -71,10 +90,21 @@ public async Task<IActionResult> CloneInline([FromBody] IdRequest request, Cance
7190
var source = await store.GetByIdAsync(request.Id, ct);
7291
if (source == null) return NotFound();
7392

93+
var templates = await store.GetAllAsync(ct);
94+
95+
var cloneName = BuildCloneName(source.Name);
96+
var copyNumber = 2;
97+
98+
while (HasDuplicateName(templates, cloneName))
99+
{
100+
cloneName = BuildCloneName(source.Name, copyNumber);
101+
copyNumber++;
102+
}
103+
74104
var clone = new OrganizationResourceMapTemplate
75105
{
76106
Id = Guid.NewGuid(),
77-
Name = $"{source.Name} (Copy)",
107+
Name = cloneName,
78108
Description = source.Description,
79109
Conditions = source.Conditions.Select(c => new OrganizationResourceMapCondition
80110
{
@@ -87,7 +117,8 @@ public async Task<IActionResult> CloneInline([FromBody] IdRequest request, Cance
87117
};
88118

89119
await store.UpsertAsync(clone, ct);
90-
return Json(new { id = clone.Id });
120+
121+
return CreatedAtAction(nameof(GetJson), new { id = clone.Id }, new { id = clone.Id });
91122
}
92123

93124
[HttpPost]
@@ -104,4 +135,26 @@ public async Task<IActionResult> SetDefaultInline([FromBody] IdRequest request,
104135
return Ok();
105136
}
106137

138+
private static bool HasDuplicateName(
139+
IEnumerable<OrganizationResourceMapTemplate> templates,
140+
string name,
141+
Guid? excludeId = null)
142+
{
143+
return templates.Any(t =>
144+
(!excludeId.HasValue || t.Id != excludeId.Value) &&
145+
string.Equals(t.Name, name, StringComparison.OrdinalIgnoreCase));
146+
}
147+
148+
private static string BuildCloneName(
149+
string sourceName,
150+
int? copyNumber = null)
151+
{
152+
var suffix = copyNumber.HasValue
153+
? $" (Copy {copyNumber.Value})"
154+
: " (Copy)";
155+
156+
var baseName = sourceName.Trim();
157+
158+
return baseName + suffix;
159+
}
107160
}

DotNet/Automation.UI/Services/Persistence/MongoIndexManager.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,21 @@ public void EnsureAllIndexes()
4747

4848
private void EnsureOrganizationResourceMapTemplateIndexes()
4949
{
50-
var collection = _database.GetCollection<BsonDocument>("automation_org_resource_map_templates");
51-
CreateIndexSafe(collection, new BsonDocument { { "Name", 1 } }, unique: false, "idx_name_asc");
52-
CreateIndexSafe(collection, new BsonDocument { { "IsDefault", 1 } }, unique: false, "idx_isDefault");
50+
var collection = _database.GetCollection<BsonDocument>(
51+
"automation_org_resource_map_templates");
52+
53+
// Retain Name index because GetAllAsync sorts by display Name.
54+
CreateIndexSafe(
55+
collection,
56+
new BsonDocument { { "Name", 1 } },
57+
unique: false,
58+
"idx_name_asc");
59+
60+
CreateIndexSafe(
61+
collection,
62+
new BsonDocument { { "IsDefault", 1 } },
63+
unique: false,
64+
"idx_isDefault");
5365
}
5466

5567
// --- automation_runs ---

DotNet/Automation.UI/Services/Persistence/MongoOrganizationResourceMapTemplateStore.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using System.Text.Json;
2-
using Automation.UI.Models;
1+
using Automation.UI.Models;
32
using MongoDB.Driver;
3+
using System.Text.Json;
44

55
namespace Automation.UI.Services.Persistence;
66

@@ -72,7 +72,7 @@ public async Task DeleteAsync(Guid id, CancellationToken ct = default)
7272
private static OrganizationResourceMapTemplateDocument ToDocument(OrganizationResourceMapTemplate model) => new()
7373
{
7474
Id = model.Id,
75-
Name = model.Name,
75+
Name = model.Name.Trim(),
7676
Description = model.Description,
7777
IsSystem = model.IsSystem,
7878
IsDefault = model.IsDefault,
@@ -87,7 +87,8 @@ public async Task DeleteAsync(Guid id, CancellationToken ct = default)
8787
Description = doc.Description,
8888
IsSystem = doc.IsSystem,
8989
IsDefault = doc.IsDefault,
90-
Conditions = Deserialize<List<OrganizationResourceMapCondition>>(doc.ConditionsJson) ?? [],
90+
Conditions = Deserialize<List<OrganizationResourceMapCondition>>(
91+
doc.ConditionsJson) ?? [],
9192
UpdatedAt = doc.UpdatedAt
9293
};
9394

DotNet/Automation.UI/Services/Persistence/OrganizationResourceMapTemplateDocument.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ internal sealed class OrganizationResourceMapTemplateDocument
88
[BsonId]
99
[BsonRepresentation(BsonType.String)]
1010
public Guid Id { get; set; }
11-
1211
public string Name { get; set; } = string.Empty;
1312
public string? Description { get; set; }
1413
public bool IsSystem { get; set; }

DotNet/Automation.UI/Views/OrganizationResourceMaps/Index.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@
462462
const conditions = collectConditions();
463463
const model={
464464
id: document.getElementById('MapId').value || crypto.randomUUID(),
465-
name: document.getElementById('MapName').value,
465+
name: document.getElementById('MapName').value.trim(),
466466
description: document.getElementById('MapDescription').value || null,
467467
conditions: conditions,
468468
isSystem:false,

0 commit comments

Comments
 (0)