Ejemplo n.º 1
0
        public Command Resolve(string text)
        {
            try
            {
                var commands = Enum.GetValues(typeof(CommandType));
                foreach (var cmd in commands)
                {
                    var cmdEnum = ((CommandType)cmd);
                    var words   = cmdEnum.ToWords();
                    var matched = Match(text, words);
                    if (matched)
                    {
                        return(new Command(cmdEnum, text));
                    }
                }

                // 時刻(hhmm)が入力されたら AnswerToEoW とする
                var hhmm = Hhmm.Parse(text);
                if (!hhmm.IsEmpty)
                {
                    return(new Command(CommandType.AnswerToEoWWithTime, text));
                }
                return(new Command(CommandType.None, text));
            }
            catch (Exception ex)
            {
                Trace.WriteLine($"CommandResolver.Resolve failed. text: {text} - {ex.Message} - {ex.StackTrace}");
                return(new Command(CommandType.None, text));
            }
        }
Ejemplo n.º 2
0
        public async Task ModifyTimecard(string yyyymmdd, string eoWTime)
        {
            var ymd = Yyyymmdd.Parse(yyyymmdd, _currentUser.TimeZoneId);

            if (ymd.isEmpty)
            {
                Trace.WriteLine($"ModifyTimecard parse ymd failed - {yyyymmdd}");
                return;
            }

            // なしと言われたら該当日のタイムカードを削除
            if (eoWTime.Contains("なし"))
            {
                await _monthlyTimecardRepo.DeleteTimecardRecord(_currentUser.UserId, ymd);

                // ユーザーのタイムゾーンでの現在時刻
                var tzUser    = TimeZoneInfo.FindSystemTimeZoneById(_currentUser.TimeZoneId);
                var nowUserTz = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzUser);
                var today     = Yyyymmdd.FromDate(nowUserTz);

                // 削除日が今日だったら、また聞き始めるようにステータスを削除する
                if (ymd.Equals(today))
                {
                    var stateEntity = await _conversationStateRepo.GetStatusByUserId(_currentUser.UserId);

                    if (stateEntity != null)
                    {
                        stateEntity.State = AskingState.None;
                        await _conversationStateRepo.UpsertState(stateEntity);
                    }
                }
            }
            else
            {
                // 該当日のタイムカードを更新または追加
                var hhmm = Hhmm.Parse(eoWTime);
                if (hhmm.IsEmpty)
                {
                    Trace.WriteLine($"ModifyTimecard parse hhmm failed - {hhmm}");
                    return;
                }

                await PunchEoW(ymd, hhmm);
            }
        }
        public async Task UpsertTimecardRecord(string userId, Yyyymmdd ymd, Hhmm hm)
        {
            // 既存の月次タイムカードを得る
            var monthlyTimecardEntity = await GetMonthlyTimecardsByYearMonth(userId, ymd.Year, ymd.Month);

            IList <TimecardRecord> timecardRecords = new List <TimecardRecord>();

            if (monthlyTimecardEntity == null)
            {
                // 得られなかったら新たに月次タイムカードを作る
                monthlyTimecardEntity = new MonthlyTimecardEntity(_paritionKey, userId, $"{ymd.Year:0000}{ymd.Month:00}");
            }
            else
            {
                // 得られたら、月次タイムカード内にJsonで入っている各日のタイムカード情報をデシリアライズして得る
                timecardRecords = monthlyTimecardEntity.GetTimecardsAsList();
            }

            // 月次タイムカード内の各日のタイムカード群から該当日を検索する
            var hit = timecardRecords.FirstOrDefault(x => x.Day == ymd.Day);

            if (hit != null)
            {
                // 見つかればその終業時刻を書き換え
                hit.EoWTime = $"{hm.Hour:00}{hm.Minute:00}";
            }
            else
            {
                // 見つからなければ新たにタイムカードレコードを作って追加
                var newRecord = new TimecardRecord()
                {
                    Day     = ymd.Day,
                    EoWTime = $"{hm.Hour:00}{hm.Minute:00}"
                };
                timecardRecords.Add(newRecord);
            }

            // Jsonにシリアライズして戻す
            monthlyTimecardEntity.SetTimecardDataJsonFromList(timecardRecords);


            // 月次タイムカードをUpsertする
            var upsertOp = TableOperation.InsertOrReplace(monthlyTimecardEntity);
            await _monthlyTimecardTable.ExecuteAsync(upsertOp);
        }
Ejemplo n.º 4
0
        public async Task PunchEoW(Yyyymmdd ymd, Hhmm hm)
        {
            // 指定された日付の終業時刻を、指定された時刻で更新する
            var monthlyTimecardRepo = new MonthlyTimecardRepository();
            await monthlyTimecardRepo.UpsertTimecardRecord(_currentUser.UserId, ymd, hm);

            // 当日ならもう聞かないようにステータスを打刻済みに更新
            var stateEntity = await _conversationStateRepo.GetStatusByUserId(_currentUser.UserId);

            var targetYmd = Yyyymmdd.Parse(stateEntity.TargetDate, _currentUser.TimeZoneId);

            if (stateEntity != null && ymd.Equals(targetYmd))
            {
                stateEntity.State = AskingState.Punched;
                await _conversationStateRepo.UpsertState(stateEntity);
            }

            return;
        }
Ejemplo n.º 5
0
        public async Task <(Yyyymmdd ymd, Hhmm hm)> PunchEoW(string hhmmText)
        {
            var tzUser    = TimeZoneInfo.FindSystemTimeZoneById(_currentUser.TimeZoneId);
            var nowUserTz = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tzUser); // ユーザーのタイムゾーンでの現在時刻

            var hhmm = Hhmm.Parse(hhmmText);
            var ymd  = Yyyymmdd.FromDate(nowUserTz);

            // パース失敗していたら処理しない
            if (hhmm.IsEmpty || ymd.isEmpty)
            {
                Trace.WriteLine($"PunchEoW parse hhmm failed - {hhmmText}");
                return(ymd, hhmm);
            }

            await PunchEoW(ymd, hhmm);

            return(ymd, hhmm);
        }
Ejemplo n.º 6
0
        PunchEoW(ConversationStateEntity stateEntity)
        {
            var hhmm = Hhmm.Parse(stateEntity.TargetTime);
            // 該当日のタイムカードの終業時刻を更新
            var ymd = Yyyymmdd.Parse(stateEntity.TargetDate, _currentUser.TimeZoneId);

            if (ymd.isEmpty || hhmm.IsEmpty)
            {
                Trace.WriteLine($"PunchEoW parse ymd, hhmm failed - {ymd}, {hhmm}");
                return(ymd, hhmm);
            }

            var monthlyTimecardRepo = new MonthlyTimecardRepository();
            await monthlyTimecardRepo.UpsertTimecardRecord(_currentUser.UserId, ymd, hhmm);

            // 打刻済みにして更新
            stateEntity.State = AskingState.Punched;
            await _conversationStateRepo.UpsertState(stateEntity);

            return(ymd, hhmm);
        }
Ejemplo n.º 7
0
        public void Send(bool disableFilter)
        {
            var appId       = ConfigurationManager.AppSettings["MicrosoftAppId"];
            var appPassword = ConfigurationManager.AppSettings["MicrosoftAppPassword"];

            var nowUtc = DateTime.Now.ToUniversalTime();

            Log($"UTC現在時刻- {nowUtc}");

            var usersRepo = new UsersRepository();
            var users     = usersRepo.GetAllUsers().RunAsSync();

            var conversationStateRepo = new ConversationStateRepository();

            Log($"処理ユーザー数: {users.Count()}");
            foreach (var user in users)
            {
                try
                {
                    Log($"ユーザー: {user.NickName}({user.UserId}) ---");

                    var startHhmm = Hhmm.Parse(user.AskEndOfWorkStartTime);
                    var endHhmm   = Hhmm.Parse(user.AskEndOfWorkEndTime);

                    // 24時超過分をオフセットして比較する
                    // 19:00~26:00 の設定だった時に、翌日の深夜1時(25時)も送信対象となるように。
                    var offsetHour = endHhmm.Hour - 23;
                    if (offsetHour < 0)
                    {
                        offsetHour = 0;
                    }
                    Log($"オフセット時間: {offsetHour}h");

                    var tzUser    = TimeZoneInfo.FindSystemTimeZoneById(user.TimeZoneId);
                    var nowUserTz = TimeZoneInfo.ConvertTimeFromUtc(nowUtc, tzUser); // ユーザーのタイムゾーンでの現在時刻
                    Log($"ユーザータイムゾーンの現在時刻(オフセット前): {nowUserTz}");
                    nowUserTz = nowUserTz.AddHours(-offsetHour);
                    var nowHour         = nowUserTz.Hour + offsetHour;
                    var nowStepedMinute = nowUserTz.Minute / 30 * 30;
                    Log($"ユーザータイムゾーンの現在時刻(オフセット前、丸め後): {nowHour}時{nowStepedMinute:00}分");

                    var startTotalMinute = (startHhmm.Hour - offsetHour) * 60 + startHhmm.Minute;
                    var endTotalMinute   = (endHhmm.Hour - offsetHour) * 60 + endHhmm.Minute;
                    var nowTotalMinute   = (nowHour - offsetHour) * 60 + nowStepedMinute;
                    Log($"ユーザータイムゾーンの現在時刻(オフセット後、丸め後): {(nowHour - offsetHour)}時{nowStepedMinute:00}分");
                    Log($"判定時刻範囲(オフセット前): {startHhmm.Hour}時{startHhmm.Minute:00}分~{endHhmm.Hour}時{endHhmm.Minute:00}分");
                    Log($"判定時刻範囲(オフセット後): {(startHhmm.Hour - offsetHour)}時{startHhmm.Minute:00}分~{(endHhmm.Hour - offsetHour)}時{endHhmm.Minute:00}分");

                    if (startTotalMinute >= endTotalMinute)
                    {
                        Log($"{user.UserId} は、開始時刻({user.AskEndOfWorkStartTime})と終了時刻({user.AskEndOfWorkEndTime})が逆転しているので何もしない。");
                        continue;
                    }

                    var nowUserTzDateText = $"{nowUserTz.Year:0000}/{nowUserTz.Month:00}/{nowUserTz.Day:00}";  // ユーザーTZ現在時刻を文字列化

                    var stateEntity = conversationStateRepo.GetStatusByUserId(user.UserId).RunAsSync();

                    var currentTargetDate = stateEntity?.TargetDate ?? "2000/01/01";

                    // ターゲット日付と現在時刻が同じで、
                    // 打刻済/今日はもう聞かないで/休日だったら何もしない
                    var currentState = stateEntity?.State ?? AskingState.None;
                    if (!disableFilter)
                    {
                        if (string.Equals(nowUserTzDateText, currentTargetDate) &&
                            (currentState == AskingState.DoNotAskToday || currentState == AskingState.Punched || currentState == AskingState.TodayIsOff))
                        {
                            Log($"ターゲット日付({currentTargetDate})とユーザーTZ現在日付({nowUserTzDateText})が同じで、State が {currentState} なので何もしない");
                            continue;
                        }

                        // 今日の曜日はユーザー設定で有効か?
                        var enableDayOfWeek = (user.DayOfWeekEnables?.Length ?? 0) - 1 > (int)nowUserTz.DayOfWeek ?
                                              (user.DayOfWeekEnables[(int)nowUserTz.DayOfWeek] == '1') : true;
                        if (!enableDayOfWeek)
                        {
                            Log($"ユーザーTZ現在日付({nowUserTzDateText})の曜日は仕事が休みなので何もしない");
                            continue;
                        }

                        // FIXME 毎年ある祝日か、単発の休日かの管理が面倒なので、とりまオミットしておく
                        //// 祝日か?(面倒だからJson文字列のまま検索しちゃう)
                        //var isHoliday = user.HolidaysJson?.Contains($"\"{nowUserTz:M/d}\"") ?? false; // "6/1" みたいにダブルコートして検索すればいいっしょ
                        //if (isHoliday)
                        //{
                        //    Log($"ユーザーTZ現在日付({nowUserTzText})の休日に設定されている何もしない");
                        //    continue;
                        //}

                        var containsTimeRange = startTotalMinute <= nowTotalMinute && nowTotalMinute <= endTotalMinute;

                        // 聞き取り終了時刻を過ぎていたらStateをNoneにする
                        // AskingEoW のまま y を打たれると打刻できてしまうので。
                        if (startTotalMinute > endTotalMinute)
                        {
                            conversationStateRepo.UpsertState(
                                user.PartitionKey, user.UserId, AskingState.None, $"{endHhmm.Hour:00}{endHhmm.Minute:00}",
                                nowUserTzDateText).RunAsSync();
                        }

                        if (!containsTimeRange)
                        {
                            Log($"現在時刻({nowUserTz}) が {user.AskEndOfWorkStartTime} から {user.AskEndOfWorkEndTime} の範囲外なので何もしない");
                            continue;
                        }
                    }

                    var conversationRef = JsonConvert.DeserializeObject <ConversationReference>(user.ConversationRef);

                    MicrosoftAppCredentials.TrustServiceUrl(conversationRef.ServiceUrl);
                    var connector = new ConnectorClient(new Uri(conversationRef.ServiceUrl), appId, appPassword);

                    var userAccount = new ChannelAccount(id: user.UserId);
                    var res         = connector.Conversations.CreateDirectConversation(conversationRef.Bot, userAccount);

                    // conversationRef.GetPostToUserMessage() では Slack にポストできなかったので、
                    // 普通に CreateMessageActivity した。
                    var message = Activity.CreateMessageActivity();
                    message.From         = conversationRef.Bot;
                    message.Recipient    = userAccount;
                    message.Conversation = new ConversationAccount(id: res.Id);

                    message.Text = $"{user.NickName} さん、お疲れさまです。{nowHour}時{nowStepedMinute:00}分 です、今日のお仕事は終わりましたか?\n\n" +
                                   $"--\n\ny:終わった\n\nn:終わってない\n\nd:今日は徹夜";
                    message.Locale = "ja-Jp";

                    //message.Attachments.Add(new Attachment()
                    //{
                    //    ContentType = AdaptiveCard.ContentType,
                    //    Content = MakeAdaptiveCard()
                    //});

                    connector.Conversations.SendToConversation((Activity)message);

                    conversationStateRepo.UpsertState(
                        user.PartitionKey, conversationRef.User.Id, AskingState.AskingEoW, $"{nowHour:00}{nowStepedMinute:00}",
                        nowUserTzDateText).RunAsSync();

                    Log($"メッセージを送信しました。 ({message.Text})");
                }
                catch (Exception ex)
                {
                    Log($"メッセージ送信に失敗しました。 ({ex.Message} - {ex.StackTrace})");
                }
            }
        }
Ejemplo n.º 8
0
        private async Task ReceivedPreferenceTextAsync(IDialogContext context, IAwaitable <string> result)
        {
            var text  = await result;
            var error = string.Empty;

            var cancelTerms = CommandType.Cancel.ToWords();

            if (cancelTerms.Any(x => string.Equals(x, text)))
            {
                context.Fail(new OperationCanceledException($"Cancel by user - {text}"));
                return;
            }

            switch (_prefType)
            {
            case UserPreferenceType.NickName:
                break;

            case UserPreferenceType.EndOfWorkTime:
            case UserPreferenceType.EndOfConfirmTime:
            {
                // hhmm のバリデーション
                var hhmm = Hhmm.Parse(text);
                if (hhmm.IsEmpty)
                {
                    error = "時刻は hhmm の形式で入力して下さい。";
                }
            }
            break;

            case UserPreferenceType.DayOfWeekEnables:
            {
                // 休みの曜日のバリデーション
                if (!text.All(x => User.WEEKDAYS.Contains(x.ToString())))
                {
                    error = "休みの曜日は「月火水木金土日」から「土日」、「水金日」などの形式で入力して下さい。";
                }
            }
            break;

            case UserPreferenceType.TimeZoneId:
            {
                // タイムゾーンのバリデーション
                try
                {
                    TimeZoneInfo.FindSystemTimeZoneById(text);
                }
                catch (Exception)
                {
                    error = "有効なタイムゾーンではありません。";
                }
            }
            break;

            default:
                break;
            }

            if (string.IsNullOrEmpty(error))
            {
                // 値を返す
                context.Done(new Result(_prefType, text));
            }
            else
            {
                // 再度入力させる
                await context.PostAsync(error);
                await CommandPreferenceMenu(context, _prefType);
            }
        }