Skip to content

Commit 846b784

Browse files
committed
Improves group moving and adds url anchors for scrolling to moved competency/group.
1 parent 1983c15 commit 846b784

File tree

7 files changed

+218
-96
lines changed

7 files changed

+218
-96
lines changed

DigitalLearningSolutions.Data.Migrations/Properties/Resources.Designer.cs

Lines changed: 13 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DigitalLearningSolutions.Data.Migrations/Properties/Resources.resx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,10 +493,10 @@
493493
<data name="TD-5759_CreateOrAlterSelfAssessmentReportSPandTVF-Fix_UP" type="System.Resources.ResXFileRef, System.Windows.Forms">
494494
<value>..\Scripts\TD-5759_CreateOrAlterSelfAssessmentReportSPandTVF-Fix_UP.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
495495
</data>
496-
<data name="TD-483-uspMoveCompetencyGroupInSelfAssessmentCreateOrAlter_UP" type="System.Resources.ResXFileRef, System.Windows.Forms">
497-
<value>..\Scripts\TD-483-uspMoveCompetencyGroupInSelfAssessmentCreateOrAlter_UP.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
498-
</data>
499496
<data name="TD-483-uspMoveCompetencyInSelfAssessmentCreateOrAlter_UP" type="System.Resources.ResXFileRef, System.Windows.Forms">
500497
<value>..\Scripts\TD-483-uspMoveCompetencyInSelfAssessmentCreateOrAlter_UP.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
501498
</data>
499+
<data name="TD-483-uspMoveCompetencyGroupInSelfAssessmentCreateOrAlter_UP" type="System.Resources.ResXFileRef, System.Windows.Forms">
500+
<value>..\Scripts\TD-483-uspMoveCompetencyGroupInSelfAssessmentCreateOrAlter_UP.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
501+
</data>
502502
</root>
Lines changed: 110 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

2-
3-
CREATE PROCEDURE usp_RenumberSelfAssessmentStructure
2+
CREATE OR ALTER PROCEDURE usp_RenumberSelfAssessmentStructure
43
@SelfAssessmentID INT
54
AS
65
BEGIN
@@ -47,68 +46,119 @@ GO
4746
CREATE OR ALTER PROCEDURE usp_MoveCompetencyGroupInSelfAssessment
4847
@SelfAssessmentID INT,
4948
@GroupID INT,
50-
@Direction NVARCHAR(10)
49+
@Direction NVARCHAR(10) -- 'up' or 'down'
5150
AS
5251
BEGIN
5352
SET NOCOUNT ON;
53+
SET XACT_ABORT ON;
5454

55-
-- Build current group ranks
56-
;WITH GroupRanks AS (
57-
SELECT
58-
CompetencyGroupID,
59-
ROW_NUMBER() OVER (ORDER BY MIN(Ordering)) AS GroupRank
60-
FROM SelfAssessmentStructure
61-
WHERE SelfAssessmentID = @SelfAssessmentID
62-
GROUP BY CompetencyGroupID
63-
)
64-
SELECT * INTO #GroupRanks FROM GroupRanks;
65-
66-
DECLARE @CurrentRank INT;
67-
SELECT @CurrentRank = GroupRank FROM #GroupRanks WHERE CompetencyGroupID = @GroupID;
68-
69-
IF @CurrentRank IS NULL
70-
BEGIN
71-
DROP TABLE #GroupRanks;
72-
RETURN; -- group not found
73-
END
74-
75-
DECLARE @SwapGroupID INT, @SwapRank INT;
76-
77-
IF @Direction = 'up'
78-
BEGIN
79-
SELECT TOP 1 @SwapGroupID = CompetencyGroupID, @SwapRank = GroupRank
80-
FROM #GroupRanks
81-
WHERE GroupRank < @CurrentRank
82-
ORDER BY GroupRank DESC;
83-
END
84-
ELSE IF @Direction = 'down'
85-
BEGIN
86-
SELECT TOP 1 @SwapGroupID = CompetencyGroupID, @SwapRank = GroupRank
87-
FROM #GroupRanks
88-
WHERE GroupRank > @CurrentRank
89-
ORDER BY GroupRank ASC;
90-
END
91-
92-
IF @SwapGroupID IS NULL
93-
BEGIN
94-
DROP TABLE #GroupRanks;
95-
RETURN; -- already at top/bottom
96-
END
55+
BEGIN TRY
56+
BEGIN TRAN;
9757

98-
/*
99-
Step 1: Temporarily bump the current group's Ordering so it sorts last
100-
(this avoids collisions before renumbering).
101-
*/
102-
UPDATE sas
103-
SET Ordering = Ordering + 100000
104-
FROM SelfAssessmentStructure sas
105-
WHERE SelfAssessmentID = @SelfAssessmentID AND CompetencyGroupID = @GroupID;
58+
/* 1) Rank groups by current Min(Ordering) (NULL groups excluded here; include if desired). */
59+
;WITH GroupRanks AS (
60+
SELECT
61+
CompetencyGroupID,
62+
MIN(Ordering) AS MinOrder,
63+
ROW_NUMBER() OVER (
64+
ORDER BY MIN(Ordering), MIN(CompetencyGroupID)
65+
) AS RankPos
66+
FROM SelfAssessmentStructure
67+
WHERE SelfAssessmentID = @SelfAssessmentID
68+
AND CompetencyGroupID IS NOT NULL
69+
GROUP BY CompetencyGroupID
70+
)
71+
SELECT *
72+
INTO #Groups
73+
FROM GroupRanks;
10674

107-
/*
108-
Step 2: Call the renumbering procedure to rebuild a clean sequence.
109-
*/
110-
EXEC usp_RenumberSelfAssessmentStructure @SelfAssessmentID;
75+
DECLARE @CurRank INT, @SwapRank INT, @SwapGroupID INT;
11176

112-
DROP TABLE #GroupRanks;
113-
END
114-
GO
77+
SELECT @CurRank = RankPos
78+
FROM #Groups
79+
WHERE CompetencyGroupID = @GroupID;
80+
81+
IF @CurRank IS NULL
82+
BEGIN
83+
DROP TABLE #Groups;
84+
COMMIT TRAN; RETURN; -- nothing to do
85+
END
86+
87+
IF LOWER(@Direction) = 'up'
88+
SET @SwapRank = @CurRank - 1;
89+
ELSE IF LOWER(@Direction) = 'down'
90+
SET @SwapRank = @CurRank + 1;
91+
ELSE
92+
BEGIN
93+
DROP TABLE #Groups;
94+
ROLLBACK TRAN; THROW 50000, 'Direction must be ''up'' or ''down''.', 1;
95+
END
96+
97+
SELECT @SwapGroupID = CompetencyGroupID
98+
FROM #Groups
99+
WHERE RankPos = @SwapRank;
100+
101+
IF @SwapGroupID IS NULL
102+
BEGIN
103+
DROP TABLE #Groups;
104+
COMMIT TRAN; RETURN; -- already at top/bottom
105+
END
106+
107+
/* 2) Build a mapping where ONLY the two groups swap ranks; others keep theirs. */
108+
SELECT
109+
g.CompetencyGroupID,
110+
CASE
111+
WHEN g.CompetencyGroupID = @GroupID THEN @SwapRank
112+
WHEN g.CompetencyGroupID = @SwapGroupID THEN @CurRank
113+
ELSE g.RankPos
114+
END AS NewRank
115+
INTO #RankMap
116+
FROM #Groups g;
117+
118+
/* 3) Choose a block size big enough to keep groups separated when we recompute.
119+
Using count(rows in this self assessment) + 10 is a safe dynamic choice. */
120+
DECLARE @Block INT =
121+
(SELECT COUNT(*) FROM SelfAssessmentStructure WHERE SelfAssessmentID = @SelfAssessmentID) + 10;
122+
123+
/* 4) Recompute EVERY row’s Ordering from the rank map (this sets the new global order).
124+
We preserve within-group relative order using the existing Ordering (and ID as tiebreak). */
125+
;WITH NewOrders AS (
126+
SELECT
127+
s.ID,
128+
(m.NewRank * @Block)
129+
+ ROW_NUMBER() OVER (
130+
PARTITION BY s.CompetencyGroupID
131+
ORDER BY s.Ordering, s.ID
132+
) AS NewOrdering
133+
FROM SelfAssessmentStructure s
134+
JOIN #RankMap m
135+
ON m.CompetencyGroupID = s.CompetencyGroupID
136+
WHERE s.SelfAssessmentID = @SelfAssessmentID
137+
)
138+
UPDATE s
139+
SET s.Ordering = n.NewOrdering
140+
FROM SelfAssessmentStructure s
141+
JOIN NewOrders n ON n.ID = s.ID;
142+
143+
/* 5) (Optional) Compress to 1..N while keeping the just-established relative order. */
144+
;WITH Ordered AS (
145+
SELECT ID,
146+
ROW_NUMBER() OVER (ORDER BY Ordering, ID) AS Seq
147+
FROM SelfAssessmentStructure
148+
WHERE SelfAssessmentID = @SelfAssessmentID
149+
)
150+
UPDATE s
151+
SET s.Ordering = o.Seq
152+
FROM SelfAssessmentStructure s
153+
JOIN Ordered o ON o.ID = s.ID;
154+
155+
DROP TABLE #Groups;
156+
DROP TABLE #RankMap;
157+
158+
COMMIT TRAN;
159+
END TRY
160+
BEGIN CATCH
161+
IF @@TRANCOUNT > 0 ROLLBACK TRAN;
162+
THROW;
163+
END CATCH
164+
END;

DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
namespace DigitalLearningSolutions.Data.DataServices
22
{
33
using Dapper;
4-
using DigitalLearningSolutions.Data.Models.Common;
54
using DigitalLearningSolutions.Data.Models.CompetencyAssessments;
6-
using DigitalLearningSolutions.Data.Models.Frameworks;
7-
using DigitalLearningSolutions.Data.Models.Frameworks.Import;
8-
using DocumentFormat.OpenXml.Wordprocessing;
95
using Microsoft.Extensions.Logging;
106
using System;
117
using System.Collections.Generic;
128
using System.Data;
139
using System.Linq;
14-
using System.Threading.Tasks;
1510

1611
public interface ICompetencyAssessmentDataService
1712
{
@@ -61,6 +56,14 @@ int categoryId
6156
bool UpdateSelectCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
6257
bool UpdateOptionalCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
6358
bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
59+
void MoveCompetencyInSelfAssessment(int competencyAssessmentId,
60+
int competencyId,
61+
string direction
62+
);
63+
void MoveCompetencyGroupInSelfAssessment(int competencyAssessmentId,
64+
int groupId,
65+
string direction
66+
);
6467

6568
//INSERT DATA
6669
int InsertCompetencyAssessment(int adminId, int centreId, string competencyAssessmentName);
@@ -70,7 +73,7 @@ int categoryId
7073
//DELETE DATA
7174
bool RemoveFrameworkCompetenciesFromAssessment(int competencyAssessmentId, int frameworkId);
7275
bool RemoveCompetencyFromAssessment(int competencyAssessmentId, int competencyId);
73-
}
76+
}
7477

7578
public class CompetencyAssessmentDataService : ICompetencyAssessmentDataService
7679
{
@@ -732,5 +735,24 @@ public bool RemoveCompetencyFromAssessment(int competencyAssessmentId, int compe
732735
}
733736
return true;
734737
}
738+
739+
public void MoveCompetencyInSelfAssessment(int competencyAssessmentId, int competencyId, string direction)
740+
{
741+
connection.Execute(
742+
"usp_MoveCompetencyInSelfAssessment",
743+
new { SelfAssessmentID = competencyAssessmentId, CompetencyID = competencyId, Direction = direction },
744+
commandType: CommandType.StoredProcedure
745+
);
746+
747+
}
748+
749+
public void MoveCompetencyGroupInSelfAssessment(int competencyAssessmentId, int groupId, string direction)
750+
{
751+
connection.Execute(
752+
"usp_MoveCompetencyGroupInSelfAssessment",
753+
new { SelfAssessmentID = competencyAssessmentId, GroupID = groupId, Direction = direction },
754+
commandType: CommandType.StoredProcedure
755+
);
756+
}
735757
}
736758
}

DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
namespace DigitalLearningSolutions.Web.Controllers.CompetencyAssessmentsController
22
{
3+
using DigitalLearningSolutions.Data.Enums;
34
using DigitalLearningSolutions.Data.Models.CompetencyAssessments;
5+
using DigitalLearningSolutions.Data.Models.Frameworks;
6+
using DigitalLearningSolutions.Data.Models.SelfAssessments;
7+
using DigitalLearningSolutions.Web.Attributes;
8+
using DigitalLearningSolutions.Web.Helpers;
9+
using DigitalLearningSolutions.Web.Models.Enums;
410
using DigitalLearningSolutions.Web.ViewModels.CompetencyAssessments;
511
using Microsoft.AspNetCore.Mvc;
12+
using Microsoft.AspNetCore.Mvc.Rendering;
613
using Microsoft.Extensions.Logging;
14+
using Serilog.Extensions.Hosting;
715
using System.Collections.Generic;
8-
using DigitalLearningSolutions.Data.Enums;
9-
using DigitalLearningSolutions.Web.Attributes;
10-
using DigitalLearningSolutions.Web.Models.Enums;
11-
using DigitalLearningSolutions.Web.Helpers;
1216
using System.Linq;
13-
using Microsoft.AspNetCore.Mvc.Rendering;
14-
using Serilog.Extensions.Hosting;
1517

1618
public partial class CompetencyAssessmentsController
1719
{
@@ -594,5 +596,37 @@ public IActionResult DeleteCompetency(int competencyAssessmentId, int competency
594596
competencyAssessmentService.RemoveCompetencyFromAssessment(competencyAssessmentId, competencyId);
595597
return RedirectToAction("ViewSelectedCompetencies", new { competencyAssessmentId });
596598
}
599+
public IActionResult MoveCompetencyInSelfAssessment(int competencyAssessmentId, int competencyId, string direction)
600+
{
601+
var adminId = GetAdminID();
602+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
603+
if (competencyAssessmentBase == null)
604+
{
605+
logger.LogWarning($"Failed to load Competencies page for competencyAssessmentId: {competencyAssessmentId} adminId: {adminId}");
606+
return StatusCode(500);
607+
}
608+
if (competencyAssessmentBase.UserRole < 2)
609+
{
610+
return StatusCode(403);
611+
}
612+
competencyAssessmentService.MoveCompetencyInSelfAssessment(competencyAssessmentId, competencyId, direction);
613+
return new RedirectResult(Url.Action("ViewSelectedCompetencies", new { competencyAssessmentId }) + "#competency-" + competencyId.ToString());
614+
}
615+
public IActionResult MoveCompetencyGroupInSelfAssessment(int competencyAssessmentId, int groupId, string direction)
616+
{
617+
var adminId = GetAdminID();
618+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
619+
if (competencyAssessmentBase == null)
620+
{
621+
logger.LogWarning($"Failed to load Competencies page for competencyAssessmentId: {competencyAssessmentId} adminId: {adminId}");
622+
return StatusCode(500);
623+
}
624+
if (competencyAssessmentBase.UserRole < 2)
625+
{
626+
return StatusCode(403);
627+
}
628+
competencyAssessmentService.MoveCompetencyGroupInSelfAssessment(competencyAssessmentId, groupId, direction);
629+
return new RedirectResult(Url.Action("ViewSelectedCompetencies", new { competencyAssessmentId }) + "#group-" + groupId.ToString());
630+
}
597631
}
598632
}

0 commit comments

Comments
 (0)