feat(templates/ui): manage compositions from the tree
Move composition CRUD off the TemplateEdit page and onto the tree context menu, matching Aveva's Template Toolbox flow. - New ComposeIntoDialog: pick a parent template, slot name (defaults to the source template's name). - "Compose into…" on every base template's context menu (kebab + right click) opens the dialog and calls AddCompositionAsync. - "Rename…" on composition leaves opens a prompt and calls TemplateService.RenameCompositionAsync. The owning composition row AND its owned derived template are renamed atomically; duplicate slot names or derived-name collisions abort with a clear error. - "Delete" on composition leaves confirms + cascade-deletes the composition (and its derived template via DeleteCompositionAsync). - "New Derived Template" menu item renamed to "New Inheriting Template" to disambiguate from the new derive-on-compose meaning. TemplateEdit's Compositions tab, Add Composition form, and Add/DeleteComposition handlers + state fields are deleted — the tree is now the single source of truth.
This commit is contained in:
@@ -654,6 +654,50 @@ public class TemplateService
|
||||
return Result<TemplateComposition>.Success(composition);
|
||||
}
|
||||
|
||||
public async Task<Result<TemplateComposition>> RenameCompositionAsync(
|
||||
int compositionId,
|
||||
string newInstanceName,
|
||||
string user,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(newInstanceName))
|
||||
return Result<TemplateComposition>.Failure("Slot name is required.");
|
||||
|
||||
var composition = await _repository.GetTemplateCompositionByIdAsync(compositionId, cancellationToken);
|
||||
if (composition == null)
|
||||
return Result<TemplateComposition>.Failure($"Composition with ID {compositionId} not found.");
|
||||
|
||||
if (composition.InstanceName == newInstanceName) return Result<TemplateComposition>.Success(composition);
|
||||
|
||||
var owner = await _repository.GetTemplateByIdAsync(composition.TemplateId, cancellationToken);
|
||||
if (owner == null)
|
||||
return Result<TemplateComposition>.Failure($"Owning template with ID {composition.TemplateId} not found.");
|
||||
|
||||
if (owner.Compositions.Any(c => c.Id != compositionId && c.InstanceName == newInstanceName))
|
||||
return Result<TemplateComposition>.Failure(
|
||||
$"Slot name '{newInstanceName}' already exists on '{owner.Name}'.");
|
||||
|
||||
var derived = await _repository.GetTemplateByIdAsync(composition.ComposedTemplateId, cancellationToken);
|
||||
if (derived != null && derived.IsDerived && derived.OwnerCompositionId == compositionId)
|
||||
{
|
||||
var newDerivedName = $"{owner.Name}.{newInstanceName}";
|
||||
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
|
||||
if (allTemplates.Any(t => t.Id != derived.Id && t.Name == newDerivedName))
|
||||
return Result<TemplateComposition>.Failure(
|
||||
$"Cannot rename derived template to '{newDerivedName}': a template with that name already exists.");
|
||||
|
||||
derived.Name = newDerivedName;
|
||||
await _repository.UpdateTemplateAsync(derived, cancellationToken);
|
||||
}
|
||||
|
||||
composition.InstanceName = newInstanceName;
|
||||
await _repository.UpdateTemplateCompositionAsync(composition, cancellationToken);
|
||||
await _auditService.LogAsync(user, "Update", "TemplateComposition", compositionId.ToString(), newInstanceName, composition, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return Result<TemplateComposition>.Success(composition);
|
||||
}
|
||||
|
||||
public async Task<Result<bool>> DeleteCompositionAsync(
|
||||
int compositionId,
|
||||
string user,
|
||||
|
||||
Reference in New Issue
Block a user