Skip to content

Commit 06cbfb1

Browse files
committed
Merge branch 'stable' of github.com:microting/eform-backendconfiguration-plugin into stable
2 parents 6f81172 + 4db72bb commit 06cbfb1

6 files changed

Lines changed: 424 additions & 93 deletions

File tree

eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn.Integration.Test/BackendConfigurationAssignmentWorkerServiceHelperTest.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ public async Task BackendConfigurationAssignmentWorkerServiceHelper_CreateDevice
136136
// Assert timeregistrationSiteAssignments
137137
Assert.That(timeregistrationSiteAssignments.Count, Is.EqualTo(31));
138138
Assert.That(timeregistrationSiteAssignments[30].SiteId, Is.EqualTo(sites[2].MicrotingUid));
139+
140+
// Newly-created AssignedSite must default UseOneMinuteIntervals to true (CreateDeviceUser path)
141+
Assert.That(timeregistrationSiteAssignments[30].UseOneMinuteIntervals, Is.True);
139142
}
140143

141144
// Should test the UpdateDeviceUser method and return success
@@ -338,6 +341,9 @@ await BackendConfigurationAssignmentWorkerServiceHelper.CreateDeviceUser(deviceU
338341
// Assert timeregistrationSiteAssignments
339342
Assert.That(timeregistrationSiteAssignments.Count, Is.EqualTo(31));
340343
Assert.That(timeregistrationSiteAssignments[30].SiteId, Is.EqualTo(sites[2].MicrotingUid));
344+
345+
// Newly-created AssignedSite must default UseOneMinuteIntervals to true (UpdateDeviceUser create path)
346+
Assert.That(timeregistrationSiteAssignments[30].UseOneMinuteIntervals, Is.True);
341347
}
342348

343349
// Should test the UpdateDeviceUser method with timeRegistration set to false and return success
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
/*
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2007 - 2026 Microting A/S
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
*/
16+
17+
namespace BackendConfiguration.Pn.Integration.Test;
18+
19+
using BackendConfiguration.Pn.Services.BackendConfigurationCalendarService;
20+
using BackendConfiguration.Pn.Services.BackendConfigurationLocalizationService;
21+
using BackendConfiguration.Pn.Services.BackendConfigurationTaskWizardService;
22+
using BackendConfiguration.Pn.Services.EventDeployService;
23+
using BackendConfiguration.Pn.Services.CalendarAssignmentReconciliation;
24+
using Microsoft.EntityFrameworkCore;
25+
using Microsoft.Extensions.Logging.Abstractions;
26+
using Microting.eForm.Infrastructure.Constants;
27+
using Microting.eForm.Infrastructure.Data.Entities;
28+
using Microting.EformBackendConfigurationBase.Infrastructure.Data.Entities;
29+
using Microting.EformBackendConfigurationBase.Infrastructure.Enum;
30+
using Microting.eFormApi.BasePn.Abstractions;
31+
using Microting.eFormApi.BasePn.Infrastructure.Models.API;
32+
using Microting.ItemsPlanningBase.Infrastructure.Data.Entities;
33+
using Microting.ItemsPlanningBase.Infrastructure.Enums;
34+
using NSubstitute;
35+
36+
/// <summary>
37+
/// Regression coverage for the bug where completing a calendar event with a
38+
/// NO-mandatory-fields eForm via the in-place ToggleComplete path updated only
39+
/// the SDK Case and left PlanningCase/PlanningCaseSite untouched — so the
40+
/// completion never appeared in reportsv2 (which filters on
41+
/// PlanningCase.MicrotingSdkCaseDoneAt). The fix mirrors the gRPC completion
42+
/// sync (EventsGrpcService:1668-1705), writing eventStart (the scheduled,
43+
/// possibly past, moment) so the row lands in the correct report period.
44+
/// </summary>
45+
[Parallelizable(ParallelScope.Fixtures)]
46+
[TestFixture]
47+
public class CalendarCompleteInPlaceReportSyncTests : TestBaseSetup
48+
{
49+
// A minimal real eForm with a single NON-mandatory Comment field. Drives
50+
// HasMandatoryFields(template) == false so ToggleComplete completes the
51+
// case IN PLACE (RequiresForm == false) instead of returning the form path.
52+
// Copied from EventDeployServiceTest.CommentTemplateXml.
53+
private const string CommentTemplateXml = @"
54+
<?xml version='1.0' encoding='UTF-8'?>
55+
<Main>
56+
<Id>9060</Id>
57+
<Repeated>0</Repeated>
58+
<Label>CommentMain</Label>
59+
<StartDate>2017-07-07</StartDate>
60+
<EndDate>2027-07-07</EndDate>
61+
<Language>da</Language>
62+
<MultiApproval>false</MultiApproval>
63+
<FastNavigation>false</FastNavigation>
64+
<Review>false</Review>
65+
<Summary>false</Summary>
66+
<DisplayOrder>0</DisplayOrder>
67+
<ElementList>
68+
<Element type='DataElement'>
69+
<Id>9060</Id>
70+
<Label>CommentDataElement</Label>
71+
<Description><![CDATA[CommentDataElementDescription]]></Description>
72+
<DisplayOrder>0</DisplayOrder>
73+
<ReviewEnabled>false</ReviewEnabled>
74+
<ManualSync>false</ManualSync>
75+
<ExtraFieldsEnabled>false</ExtraFieldsEnabled>
76+
<DoneButtonDisabled>false</DoneButtonDisabled>
77+
<ApprovalEnabled>false</ApprovalEnabled>
78+
<DataItemList>
79+
<DataItem type='Comment'>
80+
<Id>73660</Id>
81+
<Label>CommentField</Label>
82+
<Description><![CDATA[CommentFieldDescription]]></Description>
83+
<DisplayOrder>0</DisplayOrder>
84+
<Multi>1</Multi>
85+
<GeolocationEnabled>false</GeolocationEnabled>
86+
<Split>false</Split>
87+
<Value />
88+
<ReadOnly>false</ReadOnly>
89+
<Mandatory>false</Mandatory>
90+
<Color>e8eaf6</Color>
91+
</DataItem>
92+
</DataItemList>
93+
</Element>
94+
</ElementList>
95+
</Main>";
96+
97+
[Test]
98+
public async Task ToggleComplete_InPlace_PastEvent_SyncsPlanningCaseDoneAt()
99+
{
100+
// Boot a real SDK Core and create a NO-mandatory-fields template so the
101+
// in-place completion branch is taken.
102+
var core = await GetCore();
103+
var language = await MicrotingDbContext!.Languages.FirstAsync();
104+
105+
var template = await core.TemplateFromXml(CommentTemplateXml);
106+
var templateId = await core.TemplateCreate(template);
107+
108+
var sdkSite = new Site
109+
{
110+
Name = "inplace-report-sync-site",
111+
MicrotingUid = 4344,
112+
LanguageId = language.Id,
113+
WorkflowState = Constants.WorkflowStates.Created
114+
};
115+
await MicrotingDbContext.Sites.AddAsync(sdkSite);
116+
await MicrotingDbContext.SaveChangesAsync();
117+
118+
// SDK case backing the compliance occurrence — references the
119+
// no-mandatory template via CheckListId.
120+
var sdkCase = new Microting.eForm.Infrastructure.Data.Entities.Case
121+
{
122+
SiteId = sdkSite.Id,
123+
CheckListId = templateId,
124+
Status = 66,
125+
WorkflowState = Constants.WorkflowStates.Created
126+
};
127+
await MicrotingDbContext.Cases.AddAsync(sdkCase);
128+
await MicrotingDbContext.SaveChangesAsync();
129+
130+
// Scheduled in the PAST (30 days ago). eventStart = pastDate + 9h.
131+
var pastDate = DateTime.SpecifyKind(DateTime.UtcNow.Date.AddDays(-30), DateTimeKind.Utc);
132+
var expectedDoneAt = pastDate.AddHours(9);
133+
134+
var area = new Area
135+
{
136+
Type = AreaTypesEnum.Type1, ItemPlanningTagId = 0,
137+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
138+
};
139+
await BackendConfigurationPnDbContext!.Areas.AddAsync(area);
140+
await BackendConfigurationPnDbContext.SaveChangesAsync();
141+
142+
var property = new Property
143+
{
144+
Name = $"InPlaceReportSync-{Guid.NewGuid()}", ItemPlanningTagId = 0,
145+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
146+
};
147+
await BackendConfigurationPnDbContext.Properties.AddAsync(property);
148+
await BackendConfigurationPnDbContext.SaveChangesAsync();
149+
150+
var areaRule = new AreaRule
151+
{
152+
AreaId = area.Id, PropertyId = property.Id, EformId = templateId,
153+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
154+
};
155+
await BackendConfigurationPnDbContext.AreaRules.AddAsync(areaRule);
156+
await BackendConfigurationPnDbContext.SaveChangesAsync();
157+
158+
var planning = new Planning
159+
{
160+
Enabled = true, RepeatEvery = 1, RepeatType = RepeatType.Week, StartDate = pastDate,
161+
RelatedEFormId = templateId, WorkflowState = Constants.WorkflowStates.Created,
162+
CreatedByUserId = 1, UpdatedByUserId = 1
163+
};
164+
await ItemsPlanningPnDbContext!.Plannings.AddAsync(planning);
165+
await ItemsPlanningPnDbContext.SaveChangesAsync();
166+
167+
var arp = new AreaRulePlanning
168+
{
169+
AreaRuleId = areaRule.Id, PropertyId = property.Id, AreaId = area.Id,
170+
ItemPlanningId = planning.Id, StartDate = pastDate, Status = true,
171+
RepeatType = 1, RepeatEvery = 1, DayOfWeek = 1,
172+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
173+
};
174+
await BackendConfigurationPnDbContext.AreaRulePlannings.AddAsync(arp);
175+
await BackendConfigurationPnDbContext.SaveChangesAsync();
176+
177+
var planningSite = new Microting.EformBackendConfigurationBase.Infrastructure.Data.Entities.PlanningSite
178+
{
179+
AreaRulePlanningsId = arp.Id, SiteId = sdkSite.Id,
180+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
181+
};
182+
await BackendConfigurationPnDbContext.PlanningSites.AddAsync(planningSite);
183+
await BackendConfigurationPnDbContext.SaveChangesAsync();
184+
185+
var calConfig = new CalendarConfiguration
186+
{
187+
AreaRulePlanningId = arp.Id, StartHour = 9.0, Duration = 1.0,
188+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
189+
};
190+
await BackendConfigurationPnDbContext.CalendarConfigurations.AddAsync(calConfig);
191+
await BackendConfigurationPnDbContext.SaveChangesAsync();
192+
193+
// Live (non-removed) compliance occurrence the user "clicks complete" on.
194+
var compliance = new Compliance
195+
{
196+
PlanningId = planning.Id, PropertyId = property.Id, AreaId = area.Id,
197+
Deadline = pastDate, StartDate = pastDate.AddDays(-7),
198+
MicrotingSdkCaseId = sdkCase.Id, MicrotingSdkeFormId = templateId,
199+
WorkflowState = Constants.WorkflowStates.Created
200+
};
201+
await BackendConfigurationPnDbContext.Compliances.AddAsync(compliance);
202+
await BackendConfigurationPnDbContext.SaveChangesAsync();
203+
204+
// The planning rows reportsv2 reads — initially NOT done.
205+
var planningCase = new PlanningCase
206+
{
207+
PlanningId = planning.Id, Status = 66,
208+
MicrotingSdkCaseId = sdkCase.Id, MicrotingSdkeFormId = templateId,
209+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
210+
};
211+
await ItemsPlanningPnDbContext.PlanningCases.AddAsync(planningCase);
212+
await ItemsPlanningPnDbContext.SaveChangesAsync();
213+
214+
var planningCaseSite = new PlanningCaseSite
215+
{
216+
PlanningCaseId = planningCase.Id, PlanningId = planning.Id, Status = 66,
217+
MicrotingSdkCaseId = sdkCase.Id, MicrotingSdkSiteId = (int)sdkSite.MicrotingUid!,
218+
MicrotingCheckListSitId = templateId,
219+
WorkflowState = Constants.WorkflowStates.Created, CreatedByUserId = 1, UpdatedByUserId = 1
220+
};
221+
await ItemsPlanningPnDbContext.PlanningCaseSites.AddAsync(planningCaseSite);
222+
await ItemsPlanningPnDbContext.SaveChangesAsync();
223+
224+
var userService = Substitute.For<IUserService>();
225+
userService.UserId.Returns(1);
226+
userService.GetCurrentUserLanguage().Returns(Task.FromResult(language));
227+
var coreHelper = Substitute.For<IEFormCoreService>();
228+
coreHelper.GetCore().Returns(Task.FromResult(core));
229+
var taskWizardService = Substitute.For<IBackendConfigurationTaskWizardService>();
230+
taskWizardService.DeleteTask(Arg.Any<int>()).Returns(Task.FromResult(new OperationResult(true)));
231+
232+
var service = new BackendConfigurationCalendarService(
233+
new BackendConfigurationLocalizationService(), userService,
234+
BackendConfigurationPnDbContext, coreHelper, Substitute.For<IEventDeployService>(),
235+
ItemsPlanningPnDbContext, taskWizardService,
236+
Substitute.For<ICalendarAssignmentReconciliationService>(),
237+
NullLogger<BackendConfigurationCalendarService>.Instance);
238+
239+
// Act: complete the past occurrence in place.
240+
var result = await service.ToggleComplete(arp.Id, true, compliance.Id, null, null);
241+
242+
Assert.That(result.Success, Is.True, result.Message);
243+
Assert.That(result.Model, Is.Not.Null);
244+
Assert.That(result.Model!.RequiresForm, Is.False, "no-mandatory template must complete in place");
245+
246+
// Reload from the DB (fresh, untracked) and assert the planning rows the
247+
// report reads now carry the scheduled-past done date.
248+
var reloadedCase = await ItemsPlanningPnDbContext.PlanningCases
249+
.AsNoTracking().FirstAsync(x => x.Id == planningCase.Id);
250+
var reloadedSite = await ItemsPlanningPnDbContext.PlanningCaseSites
251+
.AsNoTracking().FirstAsync(x => x.Id == planningCaseSite.Id);
252+
253+
Assert.Multiple(() =>
254+
{
255+
Assert.That(reloadedCase.Status, Is.EqualTo(100), "PlanningCase must be marked done");
256+
Assert.That(reloadedCase.MicrotingSdkCaseDoneAt, Is.Not.Null,
257+
"PlanningCase.MicrotingSdkCaseDoneAt must be populated (report filter field)");
258+
Assert.That(reloadedCase.MicrotingSdkCaseDoneAt, Is.EqualTo(expectedDoneAt),
259+
"done date must be the scheduled PAST event-start, not now");
260+
Assert.That(reloadedCase.WorkflowState, Is.EqualTo(Constants.WorkflowStates.Processed),
261+
"PlanningCase WorkflowState must be set to Processed");
262+
Assert.That(reloadedCase.DoneByUserId, Is.EqualTo(sdkCase.SiteId!.Value),
263+
"PlanningCase DoneByUserId must be the completing site");
264+
265+
Assert.That(reloadedSite.Status, Is.EqualTo(100), "PlanningCaseSite must be marked done");
266+
Assert.That(reloadedSite.MicrotingSdkCaseDoneAt, Is.EqualTo(expectedDoneAt),
267+
"PlanningCaseSite done date must be the scheduled PAST event-start");
268+
Assert.That(reloadedSite.DoneByUserId, Is.EqualTo(sdkCase.SiteId!.Value),
269+
"PlanningCaseSite DoneByUserId must be the completing site");
270+
});
271+
}
272+
}

eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn.Integration.Test/SQL/420_eform-angular-items-planning-plugin.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ CREATE TABLE `PlanningVersions` (
367367
`RepeatOccurrences` int(11) DEFAULT NULL,
368368
`DayOfWeek` int(11) DEFAULT NULL,
369369
`DayOfMonth` int(11) DEFAULT NULL,
370+
`RepeatOrdinalWeek` int(11) DEFAULT NULL,
370371
`LastExecutedTime` datetime(6) DEFAULT NULL,
371372
`Enabled` tinyint(1) NOT NULL,
372373
`RelatedEFormId` int(11) NOT NULL,
@@ -430,6 +431,7 @@ CREATE TABLE `Plannings` (
430431
`RepeatOccurrences` int(11) DEFAULT NULL,
431432
`DayOfWeek` int(11) DEFAULT NULL,
432433
`DayOfMonth` int(11) DEFAULT NULL,
434+
`RepeatOrdinalWeek` int(11) DEFAULT NULL,
433435
`LastExecutedTime` datetime(6) DEFAULT NULL,
434436
`Enabled` tinyint(1) NOT NULL,
435437
`RelatedEFormId` int(11) NOT NULL,

0 commit comments

Comments
 (0)