private async Task <SubmitResult <RosterEntry> > SaveSigninInternal(MemberSignIn value, SubmitResult <RosterEntry> result) { if (result.Errors.Count == 0) { using (var db = dbFactory()) { MemberSignIn signin; if (value.Id == 0) { signin = value; db.SignIns.Add(value); } else { signin = db.SignIns.Single(f => f.Id == value.Id); } if (signin.TimeOut != value.TimeOut) { signin.TimeOut = value.TimeOut; } if (signin.Miles != value.Miles) { signin.Miles = value.Miles; } await AssignInternal(signin, signin.EventId, db, config); result.Data = new[] { signin }.AsQueryable().Select(proj).Single(); } } return(result); }
public async Task <SubmitResult <RosterEntry> > Put(MemberSignIn value) { log.InfoFormat("User {0} updating signin for member {1} ({2}) on event {3}. New timeout = {4}", User.Identity.Name, value.MemberId, value.Name, value.EventId, value.TimeOut); return(await SaveSigninInternal(value, new SubmitResult <RosterEntry>())); }
private static MemberSignIn SplitSignin(int?eventId, MemberSignIn outer, MemberSignIn inner, IMissionLineDbContext db, IConfigSource config) { MemberSignIn front = new MemberSignIn { EventId = eventId, isMember = outer.isMember, MemberId = outer.MemberId, Name = outer.Name, TimeIn = outer.TimeIn, TimeOut = inner.TimeIn }; db.SignIns.Add(front); outer.TimeIn = inner.TimeOut ?? DateTimeOffset.UtcNow.ToOrgTime(config); return(front); }
public void Assign(string movingIn, string movingOut, string[] existingIns, string[] expectedOuts) { string memberId = "the member"; var context = TestContext.GetDefault <TestContext>(); var membersMock = new Mock <IMemberSource>(MockBehavior.Strict); var controller = new RosterController(() => context.DBMock.Object, context.ConfigMock.Object, membersMock.Object, new ConsoleLogger()); int eventId = 52; context.Events.Add(new SarEvent { Id = eventId, Opened = "3:00".TimeToDate().Value, Name = "Test Event" }); for (int i = 0; i < existingIns.Length; i += 2) { context.SignIns.Add(new MemberSignIn { Id = i, MemberId = memberId, TimeIn = existingIns[i].TimeToDate().Value, TimeOut = existingIns[i + 1].TimeToDate(), EventId = eventId }); } var target = new MemberSignIn { Id = 54, MemberId = memberId, EventId = null, TimeIn = movingIn.TimeToDate().Value, TimeOut = movingOut.TimeToDate() }; context.SignIns.Add(target); var apiResult = Task.Run(() => controller.Assign(target.Id, eventId)).Result; Assert.AreEqual(0, apiResult.Errors.Count, "expect no errors: " + string.Join("\n", apiResult.Errors.Select(f => f.Text))); Assert.AreEqual(0, context.SignIns.Count(f => f.EventId == null), "no leftovers"); var results = context.SignIns.OrderBy(f => f.TimeIn).ToList(); Assert.AreEqual(expectedOuts.Length / 2, results.Count, "expected results"); for (int i = 0; i < results.Count; i++) { Assert.AreEqual(expectedOuts[2 * i][0] == '*', results[i] == target, "target is in the right place"); Assert.AreEqual(expectedOuts[2 * i].Trim('*').TimeToDate(), results[i].TimeIn, "expected time in " + i.ToString()); Assert.AreEqual(expectedOuts[2 * i + 1].TimeToDate(), results[i].TimeOut, "expected time out " + i.ToString()); Assert.AreEqual(eventId, results[i].EventId, "is assigned to event " + i.ToString()); Assert.AreEqual(memberId, results[i].MemberId, "correct memberid " + i.ToString()); } }
public async Task <SubmitResult <RosterEntry> > Post(MemberSignIn value) { var result = new SubmitResult <RosterEntry>(); log.InfoFormat("User {0} adding signin for member {1} on event {2}", User.Identity.Name, value.MemberId, value.EventId); var memberIdClaim = ((ClaimsPrincipal)User).FindFirst("memberId"); if (memberIdClaim == null || memberIdClaim.Value != value.MemberId) { result.Errors.Add(new SubmitError("memberId", "Currently only supports signing in yourself.")); } else { var nameClaim = ((ClaimsPrincipal)User).FindFirst("name"); value.Name = nameClaim.Value; value.isMember = true; } return(await SaveSigninInternal(value, result)); }
public async Task <SubmitResult <EventEntry> > Merge(int fromId, int intoId) { var result = new SubmitResult <EventEntry>(); List <Action> notifications = new List <Action>(); var hub = this.config.GetPushHub <CallsHub>(); using (var db = dbFactory()) { var from = await db.Events.Include(f => f.SignIns).SingleOrDefaultAsync(f => f.Id == fromId); var into = await db.Events.Include(f => f.SignIns).SingleOrDefaultAsync(f => f.Id == intoId); var fromSignins = from.SignIns.ToList(); var intoSignins = into.SignIns.ToList(); foreach (var call in from.Calls) { call.EventId = intoId; } var allSignins = from.SignIns.Concat(into.SignIns).OrderBy(f => f.MemberId).ThenBy(f => f.TimeIn).ToArray(); MemberSignIn lastSignin = null; for (int i = 0; i < allSignins.Length; i++) { var thisSignin = allSignins[i]; if (lastSignin != null && lastSignin.MemberId == thisSignin.MemberId) { if (lastSignin.TimeOut == null || thisSignin.TimeIn <= lastSignin.TimeOut.Value) { if (thisSignin.TimeOut == null || lastSignin.TimeOut == null || thisSignin.TimeOut > lastSignin.TimeOut) { lastSignin.TimeOut = thisSignin.TimeOut; } var milesSum = lastSignin.Miles ?? 0 + thisSignin.Miles ?? 0; lastSignin.Miles = (lastSignin.Miles.HasValue || thisSignin.Miles.HasValue) ? milesSum : (int?)null; if (thisSignin.EventId == intoId) { into.SignIns.Remove(thisSignin); } continue; } } if (thisSignin.EventId != intoId) { into.SignIns.Add(thisSignin); } lastSignin = thisSignin; } if (string.IsNullOrWhiteSpace(into.OutgoingText)) { into.OutgoingText = from.OutgoingText; } if (string.IsNullOrWhiteSpace(into.OutgoingUrl)) { into.OutgoingUrl = from.OutgoingUrl; } if (string.IsNullOrWhiteSpace(into.DirectionsText)) { into.DirectionsText = from.DirectionsText; } if (string.IsNullOrWhiteSpace(into.DirectionsUrl)) { into.DirectionsUrl = from.DirectionsUrl; } if (into.Closed == null) { into.Closed = from.Closed; } db.Events.Remove(from); await db.SaveChangesAsync(); result.Data = compiledProj(into); hub.removedEvent(from.Id); hub.updatedEvent(result.Data); } return(new SubmitResult <EventEntry>()); }
public void Signout() { var context = VoiceTestContext.GetDefault <VoiceTestContext>(); var signin = new MemberSignIn { Id = 24, isMember = true, MemberId = context.Member.Id, Name = context.Member.Name, TimeIn = DateTime.Now.AddHours(-7.5) }; context.SignIns.Add(signin); string expectedAnswer = string.Format(Speeches.PressOneTemplate, string.Format(Speeches.PromptSignOutTemplate, string.Format(Speeches.AsMemberTemplate, context.Member.Name))) + string.Format(Speeches.PromptRecordMessageTemplate, 3) + string.Format(Speeches.PromptChangeResponder, 8) + string.Format(Speeches.PromptAdminMenu, 9); var result = AnswerCallCheckResult(context, expectedAnswer); var element = result.Root.FirstNode as XElement; Assert.AreEqual("Gather", element.Name.LocalName, "expected answer gather step"); var url = element.Attribute("action").Value; DateTimeOffset markerA = TimeUtils.GetLocalDateTime(context.ConfigMock.Object); result = Task.Run(() => context.DoApiCall(url, digits: "1")).Result.ToXDocument(); // Should mark the member signed out in the database Assert.IsTrue(signin.TimeOut.HasValue && signin.TimeOut.Value <= TimeUtils.GetLocalDateTime(context.ConfigMock.Object) && signin.TimeOut.Value >= markerA, "default time out in range"); var defaultTimeout = signin.TimeOut.Value; element = result.Root.FirstNode as XElement; Assert.AreEqual("Gather", element.Name.LocalName, "expected gather step 1"); url = element.Attribute("action").Value; // Should ask for more information Assert.AreEqual("SetTimeOut", context.GetActionMethod(url).Name, "next prompt is set time out"); // The call should be tracking the caller as signed out Assert.False(url.Contains(VoiceController.QueryFields.SignedInKey + "=1"), "action does not have signed in query value"); // Update the time out for an hour from now. result = Task.Run(() => context.DoApiCall(url, "60")).Result.ToXDocument(); Assert.AreEqual( string.Format(Speeches.SignedOutTemplate, context.Member.Name, TimeUtils.GetMiltaryTimeVoiceText(signin.TimeOut.Value)), result.Descendants("Say").First().Value, "report updated time out"); Assert.AreEqual(defaultTimeout.AddMinutes(60), signin.TimeOut.Value, "updated time out"); element = result.Root.FirstNode as XElement; Assert.AreEqual("Gather", element.Name.LocalName, "expected gather step 2"); url = element.Attribute("action").Value; Assert.AreEqual("SetMiles", context.GetActionMethod(url).Name, "next prompt is set miles"); // The caller updates their miles result = Task.Run(() => context.DoApiCall(url, "120")).Result.ToXDocument(); Assert.AreEqual(Speeches.MilesUpdated, result.Descendants("Say").First().Value, "report updated miles"); Assert.AreEqual(120, signin.Miles.Value, "updated miles"); }
internal static async Task <SubmitResult> AssignInternal(MemberSignIn signin, int?eventId, IMissionLineDbContext db, IConfigSource config) { var result = new SubmitResult(); var notifications = new List <Action>(); var hub = config.GetPushHub <CallsHub>(); var exposedSignin = await db.SignIns .Where(f => f.MemberId == signin.MemberId && f.EventId == signin.EventId && f.Id != signin.Id) .OrderByDescending(f => f.TimeIn) .FirstOrDefaultAsync(); if (exposedSignin != null) { notifications.Add(() => hub.updatedRoster(compiledProj(exposedSignin), true)); } var others = db.SignIns.Where(f => f.EventId == eventId && f.MemberId == signin.MemberId && f.Id != signin.Id).OrderBy(f => f.TimeIn).ToList(); DateTimeOffset effectiveTimeOut = signin.TimeOut ?? DateTimeOffset.MaxValue; bool rosterisLatest = true; foreach (var other in others) { var otherTimeOut = other.TimeOut ?? DateTimeOffset.MaxValue; // R: [----------> // O: [----------] // R: [----------> // O: [--------> // R: [----------] // O: [-----> // R: [--------------------] // O: [-------] // R: [---------] // O: [---> // R: [------] // O: [------] // R: [---] // O: [----] // R: [-----] // O: [-----] // R: [---] // O: [---] // Trim signins that overlap our time in. if (signin.TimeIn <= other.TimeIn) { if (effectiveTimeOut >= other.TimeIn && effectiveTimeOut <= otherTimeOut) { // roster is before the other one. It is not the latest rosterisLatest = false; notifications.Add(() => hub.updatedRoster(compiledProj(other), true)); signin.TimeOut = other.TimeIn; } else if (effectiveTimeOut > otherTimeOut) { // split roster around other. // roster is the most recent, other and front are not. var front = SplitSignin(eventId, signin, other, db, config); notifications.Add(() => hub.updatedRoster(compiledProj(front), false)); notifications.Add(() => hub.updatedRoster(compiledProj(other), false)); } } else { if (otherTimeOut > signin.TimeIn && otherTimeOut <= effectiveTimeOut) { // other overlaps on the early side other.TimeOut = signin.TimeIn; notifications.Add(() => hub.updatedRoster(compiledProj(other), false)); } else if (otherTimeOut > effectiveTimeOut) { //split other around roster var front = SplitSignin(eventId, other, signin, db, config); notifications.Add(() => hub.updatedRoster(compiledProj(front), false)); notifications.Add(() => hub.updatedRoster(compiledProj(other), true)); rosterisLatest = false; } else if (otherTimeOut < signin.TimeIn) { // other notification is not the latest. notifications.Add(() => hub.updatedRoster(compiledProj(other), false)); } } } signin.EventId = eventId; notifications.Add(() => hub.updatedRoster(compiledProj(signin), rosterisLatest)); db.SaveChanges(); foreach (var notify in notifications) { notify(); } return(result); }
public async Task <TwilioResponse> DoSignInOut(TwilioRequest request) { var response = new TwilioResponse(); using (var db = dbFactory()) { var signin = await db.SignIns.Where(f => f.MemberId == this.session.MemberId).OrderByDescending(f => f.TimeIn).FirstOrDefaultAsync(); var call = await db.Calls.SingleAsync(f => f.CallId == request.CallSid); DateTimeOffset time = TimeUtils.GetLocalDateTime(this.config); var sayDate = TimeUtils.GetMiltaryTimeVoiceText(time); if (signin == null || signin.TimeOut.HasValue) { if (this.session.IsSignedIn) { throw new InvalidOperationException("Tried to sign out when not signed in"); } signin = new MemberSignIn { MemberId = this.session.MemberId, isMember = true, Name = this.session.MemberName, TimeIn = time, EventId = (this.CurrentEvents.Count == 1) ? this.CurrentEvents[0].Id : this.session.EventId, }; db.SignIns.Add(signin); call.Actions.Add(new CallAction { Call = call, CallId = call.Id, Time = signin.TimeIn, Action = "Signed in " + signin.Name }); await db.SaveChangesAsync(); this.session.IsSignedIn = true; if (this.CurrentEvents.Count == 0) { await RosterController.AssignInternal(signin, null, db, this.config); BeginMenu(response); response.SayVoice(Speeches.SignedInUnassignedTemplate, this.session.MemberName, sayDate); await EndMenu(response); } else if (this.CurrentEvents.Count == 1) { await RosterController.AssignInternal(signin, this.CurrentEvents[0].Id, db, this.config); BeginMenu(response); response.SayVoice(Speeches.SignedInTemplate, this.CurrentEvents[0].Name, this.session.MemberName, sayDate); await EndMenu(response); } else { await RosterController.AssignInternal(signin, null, db, this.config); BuildSetEventMenu(response, string.Format(Speeches.SignedInUnassignedTemplate, this.session.MemberName, sayDate), Url.Content("~/api/voice/SetSigninEvent")); } } else { signin.TimeOut = time; call.Actions.Add(new CallAction { Call = call, CallId = call.Id, Time = time, Action = "Signed out " + this.session.MemberName }); this.session.IsSignedIn = false; await db.SaveChangesAsync(); this.config.GetPushHub <CallsHub>().updatedRoster(RosterController.GetRosterEntry(signin.Id, db), true); // add prompt for timeout beyond right now response.BeginGather(new { timeout = 10, action = GetAction("SetTimeOut") }); response.SayVoice(Speeches.SignedOutTemplate, this.session.MemberName, sayDate); response.SayVoice(Speeches.TimeoutPrompt); response.EndGather(); } } return(LogResponse(response)); }