Forms/M_SendPatternIndividualEdit.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using ChatworkBulkSender.Daos;
using ChatworkBulkSender.Dtos;
using ChatworkBulkSender.UserControls;
using ChatworkBulkSender.Utils;

namespace ChatworkBulkSender.Forms
{
    public partial class M_SendPatternIndividualEdit : Form, ICreatable<PatternMasterDto>, IUpdateble<PatternMasterDto>
    {

        public enum ButtonMode
        {
            Register, // 新規登録
            Update    // 更新
            ,
        }

        private List<int> _selectedCustomerIds = null;

        // パターン名称ヘッダー
        private PatternNameHeaderControl _patternNameHeader = null;


        // 送信パターンマスタ編集部分
        private SendContentControl _sendContent = null;

        private BtnRegisterControl _btnRegister = null;

        private BtnUpdateControl _btnUpdate = null;

        // 初期値は、新規登録
        private ButtonMode _btnMode = ButtonMode.Register;

        public event EventHandler<PatternMasterDto> DataCreated;
        public event EventHandler<PatternMasterDto> DataUpdated;
        
        // 楽観的ロック用の更新日時
        private DateTime? _currentUpdatedDate = null;
        
        // 更新対象のパターンID
        private int _patternId = 0;


        public ButtonMode BtnMode
        {
            set
            {
                _btnMode = value;
            }
        }

        public M_SendPatternIndividualEdit(ButtonMode buttonMode = ButtonMode.Register, PatternMasterDto sendPattern = null)
        {
            InitializeComponent();

            this._btnMode = buttonMode;
            
            // 更新モードの場合、楽観的ロック用のデータを保持
            if (buttonMode == ButtonMode.Update && sendPattern != null)
            {
                _currentUpdatedDate = sendPattern.UpdatedDate;
                _patternId = sendPattern.PatternId;
            }

            // 初期のウィンドウサイズ
            this.ClientSize = new Size(1600,900);

            // ウィンドウサイズの固定
            this.FormBorderStyle = FormBorderStyle.FixedSingle;

            // 初期のウィンドウ表示位置
            this.StartPosition = FormStartPosition.CenterScreen;


            _patternNameHeader = new PatternNameHeaderControl();
            _sendContent = new SendContentControl();

            if (_btnMode == ButtonMode.Update)
            {
                _btnUpdate = new BtnUpdateControl();
                _btnUpdate.Dock = DockStyle.Fill;
                panel3.Controls.Add(_btnUpdate);
            }
            else
            {
                _btnRegister = new BtnRegisterControl();
                _btnRegister.Dock = DockStyle.Fill;
                panel3.Controls.Add(_btnRegister);
            }

            // レイアウトを垂直に設定
            _sendContent.LayoutMode = LayoutMode.Vertical;

            // Dock設定
            _sendContent.Dock = DockStyle.Fill;
            _patternNameHeader.Dock = DockStyle.Fill;

            // パネルに追加、表示する
            panel1.Controls.Add(_patternNameHeader);
            panel2.Controls.Add(_sendContent);

            Label label1 = _sendContent.Controls.Find("label1",false).FirstOrDefault() as Label;
            label1.Width = 170;
            label1.Text += "(定型文)";

        }

        private void M_SenderPatternIndividualEdit_Load(object sender, EventArgs e)
        {
            this.ActiveControl = panel1;

            if (_btnMode == ButtonMode.Update)
            {
                _btnUpdate.BtnUpdateClicked += BtnUpdate_Click;
                _btnUpdate.BtnReturnClicked += BtnReturn_Click;
            }
            else
            {
                _btnRegister.BtnReturnClicked += BtnReturn_Click;
            }
            
            // フォントの統一設定を適用
            FontSettingHelper.ApplyFontsToForm(this);
            
            // ユーザーコントロールにも適用
            FontSettingHelper.ApplyFontsToControl(_patternNameHeader);
            FontSettingHelper.ApplyFontsToControl(_sendContent);
            if (_btnMode == ButtonMode.Update)
            {
                FontSettingHelper.ApplyFontsToControl(_btnUpdate);
            }
            else
            {
                FontSettingHelper.ApplyFontsToControl(_btnRegister);
            }
            
            // TabIndexの設定(フォーカス順制御)
            TabIndexHelper.SetIndividualEditScreenTabOrder(this);
        }

        /// <summary>
        /// フォームに表示するユーザーコントロールをパネルから削除した後、明示的なDisposeで解放する。
        /// </summary>
        private void DisposeViewControls()
        {
            // パネルから削除する
            panel1.Controls.Clear();
            panel2.Controls.Clear();
            panel3.Controls.Clear();

            // 削除だけでは解放されないので、明示的に解放する
            _patternNameHeader.Dispose();
            _sendContent.Dispose();

            if (_btnMode == ButtonMode.Update)
            {
                _btnUpdate.Dispose();
            }
            else
            {
                _btnRegister.Dispose();
            }
        }

        private void BtnRegister_Click(object sender, EventArgs e)
        {
            try
            {
                // バリデーションチェック
                if (!ValidateInputs())
                {
                    return;
                }

                if (_selectedCustomerIds != null)
                {
                    _selectedCustomerIds.Clear();
                }
                else
                {
                    _selectedCustomerIds = new List<int>();

                }

                var newPattern = GetPatternData();

                if (newPattern == null) { return; }

                var dao = new PatternMasterDao();

                var createdId = dao.InsertWithCustomersBatch(newPattern,_selectedCustomerIds);

                if (createdId == 1)
                {
                    DataCreated?.Invoke(this, newPattern);

                }

            }
            catch (Exception ex)
            {
                MessageBoxUtil.ShowErr($"{ex.Message}\n\n");
            }
        }

        private void BtnUpdate_Click(object sender, EventArgs e)
        {
            try
            {
                // バリデーションチェック
                if (!ValidateInputs())
                {
                    return;
                }

                // 選択された顧客IDを取得
                if (_selectedCustomerIds == null)
                {
                    _selectedCustomerIds = new List<int>();
                }
                else
                {
                    _selectedCustomerIds.Clear();
                }
                
                var dgvDestinationList = _sendContent.Controls.Find("dgvDestinationList", true).FirstOrDefault() as DataGridView;
                if (dgvDestinationList != null)
                {
                    foreach (DataGridViewRow row in dgvDestinationList.Rows)
                    {
                        if (row.Cells["送信対象"].Value != null && (bool)row.Cells["送信対象"].Value)
                        {
                            _selectedCustomerIds.Add(Convert.ToInt32(row.Cells["管理番号"].Value));
                        }
                    }
                }

                var updatedPattern = GetPatternData();
                if (updatedPattern == null) { return; }
                
                // パターンIDを設定
                updatedPattern.PatternId = _patternId;
                
                // 更新日時がない場合はエラー
                if (_currentUpdatedDate == null)
                {
                    MessageBox.Show("データの更新日時が取得できませんでした。", "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                
                var dao = new PatternMasterDao();
                
                // 更新前に最新のデータを取得して、他のユーザーが更新していないか確認
                var currentDbData = dao.GetById(_patternId);
                
                if (currentDbData != null)
                {
                    // DateTime精度問題対策:1秒以内の差は同じとみなす
                    bool isUpdatedByOthers = false;
                    if (currentDbData.UpdatedDate.HasValue && _currentUpdatedDate.HasValue)
                    {
                        var timeDiff = Math.Abs((currentDbData.UpdatedDate.Value - _currentUpdatedDate.Value).TotalSeconds);
                        isUpdatedByOthers = timeDiff > 1.0;  // 1秒以上の差がある場合のみ更新とみなす
                    }
                    else if (currentDbData.UpdatedDate != _currentUpdatedDate)
                    {
                        isUpdatedByOthers = true;
                    }
                    
                    if (isUpdatedByOthers)
                    {
                        // 他のユーザーが既に更新している
                        MessageBox.Show(MessageBoxUtil.DB_011, "エラー", 
                            MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        
                        // 最新のデータで画面を更新
                        SetPatternData(currentDbData);
                        return;
                    }
                }
                
                // 選択された顧客IDを含めて更新
                var newPatternId = dao.Update(updatedPattern, _currentUpdatedDate.Value, _selectedCustomerIds);
                
                if (newPatternId == -1)
                {
                    // 楽観的ロックエラー
                    MessageBox.Show(MessageBoxUtil.DB_011, "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    
                    // 最新のデータを取得して画面を更新
                    var latestData = dao.GetById(_patternId);
                    if (latestData != null)
                    {
                        SetPatternData(latestData);
                    }
                    return;
                }
                
                if (newPatternId > 0)
                {
                    // 更新成功後、最新のデータを取得して更新日時を更新
                    var latestData = dao.GetById(newPatternId);
                    if (latestData != null)
                    {
                        _currentUpdatedDate = latestData.UpdatedDate;
                        updatedPattern.UpdatedDate = latestData.UpdatedDate;  // イベントで渡すデータも更新
                    }
                    
                    // 更新成功
                    updatedPattern.PatternId = newPatternId;
                    DataUpdated?.Invoke(this, updatedPattern);
                    
                    MessageBox.Show(MessageBoxUtil.DB_002, "情報", 
                        MessageBoxButtons.OK, MessageBoxIcon.Information);
                    
                    _patternId = newPatternId;
                }
            }
            catch (Exception ex)
            {
                MessageBoxUtil.ShowErr($"{ex.Message}\n\n");
            }
        }

        private void BtnReturn_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void M_SendPatternIndividualEdit_FormClosing(object sender, FormClosingEventArgs e)
        {
            // 変更がない場合はメッセージを出さない
            // TODO: 変更検知機能を実装する場合はここで判定
            // 現時点では常にメッセージを出さない
            if (false) // 将来的に変更検知を実装する場合はここを修正
            {
                DialogResult result = MessageBox.Show("送信パターンマスタ 個別編集画面を終了してもよろしいですか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                if (result == DialogResult.No)
                {
                    e.Cancel = true;

                    return;
                }
            }

            // イベント登録の解除
            if (_btnMode == ButtonMode.Update)
            {
                _btnUpdate.BtnUpdateClicked -= BtnUpdate_Click;
                _btnUpdate.BtnReturnClicked -= BtnReturn_Click;
            }
            else
            {
                _btnRegister.BtnReturnClicked -= BtnReturn_Click;
            }

            // 明示的に解放する
            this.DisposeViewControls();

            // 設定メニューを表示する
            this.Owner.Show();

            // 再描写を行う
            this.Owner.Refresh();
        }

        private bool ValidateInputs()
        {
            // PatternNameHeaderControlのバリデーション
            bool isHeaderValid = _patternNameHeader.ValidateAll();
            
            // SendContentControlのバリデーション
            bool isContentValid = _sendContent.ValidateAll();
            
            return isHeaderValid && isContentValid;
        }

        /// <summary>
        /// パターンデータを画面に設定する
        /// </summary>
        /// <param name="pattern">設定するパターンデータ</param>
        public void SetPatternData(PatternMasterDto pattern)
        {
            if (pattern == null) return;
            
            var patternName = _patternNameHeader.Controls.Find("txtPatternName", false).FirstOrDefault() as TextBox;
            var templateTxt = _sendContent.Controls.Find("txtContents", true).FirstOrDefault() as TextBox;
            var sortOrder = _patternNameHeader.Controls.Find("txtSortOrder", false).FirstOrDefault() as TextBox;
            var isUnused = _patternNameHeader.Controls.Find("chkUnusedFlg", false).FirstOrDefault() as CheckBox;
            
            if (patternName != null) patternName.Text = pattern.PatternName;
            if (templateTxt != null) templateTxt.Text = pattern.TemplateText;
            if (sortOrder != null) sortOrder.Text = pattern.SortOrder.ToString();
            if (isUnused != null) isUnused.Checked = pattern.IsUnused;
            
            // パターンIDも更新
            _patternId = pattern.PatternId;
            _currentUpdatedDate = pattern.UpdatedDate;
        }
        
        private PatternMasterDto GetPatternData()
        {
            var patternName =_patternNameHeader.Controls.Find("txtPatternName",false).FirstOrDefault() as TextBox;
            var templateTxt = _sendContent.Controls.Find("txtContents", true).FirstOrDefault() as TextBox;
            var sortOrder = _patternNameHeader.Controls.Find("txtSortOrder", false).FirstOrDefault() as TextBox;
            var isUnused = _patternNameHeader.Controls.Find("chkUnusedFlg", false).FirstOrDefault() as CheckBox;
            var dgvDestinationList = _sendContent.Controls.Find("dgvDestinationList", true).FirstOrDefault() as DataGridView;
            var rdoAutomatic = _sendContent.Controls.Find("rdoAutomaticSelection",true).FirstOrDefault() as RadioButton;

            if (dgvDestinationList != null)
            {
                foreach (DataGridViewRow row in dgvDestinationList.Rows)
                {
                    if (row.Cells["送信対象"].Value != null && (bool)row.Cells["送信対象"].Value)
                    {
                        _selectedCustomerIds.Add(Convert.ToInt32(row.Cells["管理番号"].Value));
                    }
                }
            }

            // 並び順のパースを安全に行う
            if (!int.TryParse(sortOrder.Text, out int sortOrderValue))
            {
                MessageBox.Show("並び順の値が正しくありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }

            var newPattern = new PatternMasterDto
            {
                PatternName = patternName.Text,
                TemplateText = templateTxt.Text,
                SortOrder = sortOrderValue,
                IsUnused = isUnused.Checked,
            };

            return newPattern;

        }

    }
}