Forms/M_CustomerMasterIndividualEdit.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ChatworkBulkSender.Daos;
using ChatworkBulkSender.Dtos;
using ChatworkBulkSender.UserControls;
using ChatworkBulkSender.Utils;

namespace ChatworkBulkSender.Forms
{
    public partial class M_CustomerMasterIndividualEdit : Form,ICreatable<CustomerMasterDto>,IUpdateble<CustomerMasterDto>
    {
        public enum ButtonMode
        {
            Register, // 新規登録
            Update    // 更新
            ,
        }

        private CustomerIndividualEditControl _customerIndividualEdit = null;

        private BtnRegisterControl _btnRegister = null;

        private BtnUpdateControl _btnUpdate = null;

        public event EventHandler<CustomerMasterDto> DataCreated;
        public event EventHandler<CustomerMasterDto> DataUpdated;

        // 初期値は、新規登録
        private ButtonMode _btnMode = ButtonMode.Register;
        
        // データ変更検知
        private DataChangeDetector _changeDetector = new DataChangeDetector();
        
        // 楽観的ロック用の更新日時
        private DateTime? _currentUpdatedAt = null;


        public M_CustomerMasterIndividualEdit(ButtonMode buttonMode = ButtonMode.Register, CustomerMasterDto customer = null)
        {
            InitializeComponent();

            _btnMode = buttonMode;

            this.ClientSize = new Size(1600,900);
            this.MinimumSize = new Size(1200,700);
            // ウィンドウサイズを固定にする
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            this.StartPosition = FormStartPosition.CenterScreen;

            _customerIndividualEdit = new CustomerIndividualEditControl();
            _customerIndividualEdit.Dock = DockStyle.Fill;

            if (_btnMode == ButtonMode.Update)
            {
                _btnUpdate = new BtnUpdateControl();
                _btnUpdate.Dock = DockStyle.Fill;
                panel2.Controls.Add(_btnUpdate);
                if (customer != null)
                {
                    // 最新データをDBから取得
                    var dao = new CustomerMasterDao();
                    var latestData = dao.GetByManagementNumber(customer.ManagementNumber);
                    if (latestData != null)
                    {
                        _customerIndividualEdit = new CustomerIndividualEditControl(latestData);
                        // 楽観的ロック用に現在の更新日時を保持
                        _currentUpdatedAt = latestData.UpdatedAt;
                    }
                    else
                    {
                        _customerIndividualEdit = new CustomerIndividualEditControl(customer);
                        _currentUpdatedAt = customer.UpdatedAt;
                    }
                }
            }
            else
            {
                _btnRegister = new BtnRegisterControl();
                _btnRegister.Dock = DockStyle.Fill;
                panel2.Controls.Add(_btnRegister);
                _customerIndividualEdit = new CustomerIndividualEditControl();
                CheckBox chkIsUnused = _customerIndividualEdit.Controls.Find("chkUnusedData", true).FirstOrDefault() as CheckBox;
                chkIsUnused.Visible = false;
            }
            panel1.Controls.Add(_customerIndividualEdit);

        }

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

            if (_btnMode == ButtonMode.Update)
            {
                _btnUpdate.BtnUpdateClicked += BtnUpdate_Click;
                _btnUpdate.BtnReturnClicked += BtnReturn_Click;

            }
            else
            {
                _btnRegister.BtnRegisterClicked += BtnRegister_Click;
                _btnRegister.BtnReturnClicked += BtnReturn_Click;
            }
            
            // フォントの統一設定を適用
            FontSettingHelper.ApplyFontsToForm(this);
            
            // ユーザーコントロールにも適用
            FontSettingHelper.ApplyFontsToControl(_customerIndividualEdit);
            if (_btnMode == ButtonMode.Update)
            {
                FontSettingHelper.ApplyFontsToControl(_btnUpdate);
            }
            else
            {
                FontSettingHelper.ApplyFontsToControl(_btnRegister);
            }
            
            // TabIndexの設定(フォーカス順制御)
            TabIndexHelper.SetIndividualEditScreenTabOrder(this);
            
            // データ変更検知を開始
            _changeDetector.StartMonitoring(_customerIndividualEdit);
        }

        private void DisposeViewControls()
        {
            panel1.Controls.Clear();
            panel2.Controls.Clear();

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

        private void BtnRegister_Click(object sender, EventArgs e)
        {
            try
            { 
                var newCustomer = _customerIndividualEdit.GetCustomerData();

                var dao = new CustomerMasterDao();

                var createdId = dao.Insert(newCustomer);

                if (createdId == 1)
                {
                    DataCreated?.Invoke(this, newCustomer);
                    
                    // 登録成功後は変更状態をリセット
                    _changeDetector.ResetChanges();
                    
                    MessageBox.Show("登録が完了しました。", "情報", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }

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

        private void BtnUpdate_Click(object sender, EventArgs e)
        {
            try
            {
                var updatedCustomer = _customerIndividualEdit.GetCustomerData();
                
                // 更新日時がない場合はエラー
                if (_currentUpdatedAt == null)
                {
                    MessageBox.Show("データの更新日時が取得できませんでした。", "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                
                var dao = new CustomerMasterDao();
                
                // 更新前に最新のデータを取得して、他のユーザーが更新していないか確認
                var currentDbData = dao.GetByManagementNumber(updatedCustomer.ManagementNumber);
                
                // DateTime精度問題対策:1秒以内の差は同じとみなす
                bool isUpdatedByOthers = false;
                if (currentDbData != null)
                {
                    if (currentDbData.UpdatedAt.HasValue && _currentUpdatedAt.HasValue)
                    {
                        var timeDiff = Math.Abs((currentDbData.UpdatedAt.Value - _currentUpdatedAt.Value).TotalSeconds);
                        isUpdatedByOthers = timeDiff > 1.0;  // 1秒以上の差がある場合のみ更新とみなす
                    }
                    else if (currentDbData.UpdatedAt != _currentUpdatedAt)
                    {
                        isUpdatedByOthers = true;
                    }
                }
                
                if (isUpdatedByOthers)
                {
                    // 他のユーザーが既に更新している
                    MessageBox.Show(MessageBoxUtil.DB_011, "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    
                    // 最新のデータで画面を更新
                    _customerIndividualEdit.SetCustomerData(currentDbData);
                    _currentUpdatedAt = currentDbData.UpdatedAt;
                    _changeDetector.ResetChanges();
                    return;
                }
                
                var updateCount = dao.Update(updatedCustomer, _currentUpdatedAt.Value);
                
                if (updateCount == 0)
                {
                    // 楽観的ロックエラー
                    MessageBox.Show(MessageBoxUtil.DB_011, "エラー", 
                        MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    
                    // 最新のデータを取得して画面を更新
                    var refreshedData = dao.GetByManagementNumber(updatedCustomer.ManagementNumber);
                    if (refreshedData != null)
                    {
                        _customerIndividualEdit.SetCustomerData(refreshedData);
                        _currentUpdatedAt = refreshedData.UpdatedAt;
                    }
                    _changeDetector.ResetChanges();
                    return;
                }
                
                if (updateCount == 1)
                {
                    // 更新成功後、最新のデータを取得して更新日時を更新
                    var latestData = dao.GetByManagementNumber(updatedCustomer.ManagementNumber);
                    if (latestData != null)
                    {
                        _currentUpdatedAt = latestData.UpdatedAt;
                        updatedCustomer.UpdatedAt = latestData.UpdatedAt;  // イベントで渡すデータも更新
                    }
                    
                    DataUpdated?.Invoke(this, updatedCustomer);
                    
                    // 更新成功後は変更状態をリセット
                    _changeDetector.ResetChanges();
                    
                    MessageBox.Show(MessageBoxUtil.DB_002, "情報", 
                        MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
            catch (Exception ex)
            {
                MessageBoxUtil.ShowErr($"{ex.Message}\n\n");
            }
        }

        private void BtnReturn_Click(object sender, EventArgs e)
        {
            // 変更がある場合のみ確認
            if (_changeDetector.HasChanges)
            {
                DialogResult result = MessageBox.Show(
                    "編集内容が保存されていません。破棄してよろしいですか?",
                    "確認",
                    MessageBoxButtons.YesNo,
                    MessageBoxIcon.Warning);
                    
                if (result != DialogResult.Yes)
                {
                    return;
                }
            }
            
            this.Close();
        }

        private void M_CustomerIndividualEdit_FormClosing(object sender, FormClosingEventArgs e)
        {
            // 戻るボタンから呼ばれた場合は、既に確認済みなのでスキップ
            if (e.CloseReason == CloseReason.UserClosing && this.DialogResult != DialogResult.Cancel)
            {
                // 変更がある場合のみ確認
                if (_changeDetector.HasChanges)
                {
                    DialogResult result = MessageBox.Show(
                        "編集内容が保存されていません。破棄してよろしいですか?",
                        "確認",
                        MessageBoxButtons.YesNo,
                        MessageBoxIcon.Warning);

                    if (result != DialogResult.Yes)
                    {
                        e.Cancel = true;
                        return;
                    }
                }
                // 変更がない場合は確認メッセージを出さない
            }

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

            // 変更検知を停止
            _changeDetector.StopMonitoring();
            _changeDetector.Dispose();
            
            // 明示的に解放する
            this.DisposeViewControls();

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

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



}