11using Automation . UI . Models ;
22using Automation . UI . Services . Persistence ;
33using Microsoft . AspNetCore . Mvc ;
4+ using MongoDB . Driver ;
45
56namespace 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}
0 commit comments