using ClosedXML.Excel;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Drawing;
using DocumentFormat.OpenXml.EMMA;
using DocumentFormat.OpenXml.ExtendedProperties;
using DocumentFormat.OpenXml.Office2010.Excel;
using DocumentFormat.OpenXml.Office2010.ExcelAc;
using DocumentFormat.OpenXml.Office2016.Excel;
using DocumentFormat.OpenXml.Presentation;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.VariantTypes;
using DocumentFormat.OpenXml.Vml;
using DocumentFormat.OpenXml.Wordprocessing;
using ExcelToCsvToDb.Commons;
using ExcelToCsvToDb.DTO;
using ExcelToCsvToDbCommon.Class;
using KssSmaPlaLib.Batch.Commons;
using KssSmaPlaLib.Batch.Interface;
using KssSmaPlaLib.Commons;
using KssSmaPlaLib.Commons.Conversion;
using KssSmaPlaLib.DtoMapping.Attributes;
using KssSmaPlaLib.InBox;
using KssSmaPlaLib.Ini;
using KssSmaPlaLib.IO.Excel;
using KssSmaPlaLib.IO.File;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace ExcelToCsvToDb.BusinessLogic
{
    /// <summary>
    /// Excel→CSV処理クラス
    /// </summary>
    public class ExcelToCsv
    {
        /// <summary>
        /// 日付並びの年月取得モード
        /// </summary>
        private enum ENUM_EXCEL_DATE_YM_GET_MODE
        {
            None,

            /// <summary>
            /// 年月セルから取得
            /// （アルミ）
            /// </summary>
            FromYMCells,

            /// <summary>
            /// 日付型セル値より取得
            /// （MC）
            /// </summary>
            FromDateCell,

            /// <summary>
            /// 日付型特定セル値より取得
            /// （PoSp）
            /// </summary>
            FromTargetCellDate,

            /// <summary>
            /// シート名から取得
            /// </summary>
            FromSheetName,
        }

        /// <summary>
        /// 日付並びの年月取得モードのINI値（文字列）
        /// </summary>
        private const string KEY_EXCEL_DATE_YM_GET_MODE_FROM_YM_CELLS = "FromYMCells";
        private const string KEY_EXCEL_DATE_YM_GET_MODE_FROM_DATE_CELL = "FromDateCell";
        private const string KEY_EXCEL_DATE_YM_GET_MODE_FROM_TARGET_CELL_DATE = "TargetCellDate";
        private const string KEY_EXCEL_DATE_YM_GET_MODE_FROM_SHEET_NAME = "FromSheetName";

        /// <summary>
        /// ExcelToCsv機能（WeOrder版）
        /// </summary>
        /// <param name="iniFilePath">INIパス</param>
        /// <param name="sectionName">セクション名</param>
        /// <returns>結果DTO</returns>
        public FuncExcelToCsvWeOrderByIniResultDto TryFuncExcelToCsvWeOrderByIni(
            string iniFilePath,
            string sectionName)
        {
            // INIキー定義
            const string KEY_IN_FOLDER_PATH = "InFolderPath";
            const string KEY_FILE_NAME_PATTERN = "FileNamePattern";
            const string KEY_EXCEL_SHEET_CHECK_ITEMS = "ExcelSheetCheckItems";
            const string KEY_CSV_FILE_NAME = "CsvFileName";
            const string KEY_OUT_FOLDER_PATH = "OutFolderPath";
            const string KEY_FILE_DT_PREFIX = "FileDtPrefix";
            const string KEY_FILE_DT_SUFFIX = "FileDtSuffix";
            const string KEY_CSV_ENCODING = "CsvEncoding";

            // 結果DTO
            var result = new FuncExcelToCsvWeOrderByIniResultDto();

            // 現在日時
            DateTime now = DateTime.Now;

            LoggerUxer.Info($"Excel→CSV機能（溶接加工計画）を開始します。");

            // INI取得
            var iniDic = IniReferenceResolveUxer.ReadSectionWithReference(
                iniFilePath, sectionName);

            // アイテムと列レターのマッピング辞書作成
            if (!TryCreateItemColumnMappingDic(iniDic, out ItemColumnMappingDic, out var messageForMe))
            {
                LoggerUxer.Error(messageForMe);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.CreateItemExcelColumnMappingDicError;
                result.ResultMessage = messageForMe;
                return result;
            }

            // 開始日取得
            if (!CommonUxer.TryGetStartDate(iniDic, out var startDate, out messageForMe))
            {
                var msg = $"開始日の取得に失敗しました。[エラー内容:{messageForMe}]";
                LoggerUxer.Error(msg);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.StartDateError;
                result.ResultMessage = msg;
                return result;
            }
            LoggerUxer.Info($"[開始日:{startDate:yyyy/MM/dd}]");

            // 入力フォルダパス
            var inFolderPath = DictionaryUxer.GetValueOrDefault(iniDic, KEY_IN_FOLDER_PATH, string.Empty);
            if (!CheckInFolder(inFolderPath, out messageForMe))
            {
                LoggerUxer.Error(messageForMe);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.InFolderPathError;
                result.ResultMessage = messageForMe;
                return result;
            }
            LoggerUxer.Info($"[入力フォルダ:{inFolderPath}]");

            // 対象ファイル群取得
            var fileNamePattern = DictionaryUxer.GetValueOrDefault(iniDic, KEY_FILE_NAME_PATTERN, "*");
            if (!GetFiles(
                iniDic,
                inFolderPath,
                fileNamePattern,
                out List<string> files,
                out messageForMe))
            {
                LoggerUxer.Error(messageForMe);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.TargetFilesGetError;
                result.ResultMessage = messageForMe;
                return result;
            }

            if (files.Count == 0)
            {
                var msg = $"対象ファイルがありません。[{KEY_IN_FOLDER_PATH}:{inFolderPath},{KEY_FILE_NAME_PATTERN}={fileNamePattern}]";
                LoggerUxer.Warn(msg);
                result.Succeeded = true;
                result.ResultMessage = msg;
                // ワーニングでも成功
                return result;
            }

            // シートチェック項目取得
            var sheetCheckStr = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_SHEET_CHECK_ITEMS, string.Empty);
            LoggerUxer.Info($"[Excelシートチェック項目:{sheetCheckStr}]");
            if (!TryGetExcelSheetCheckItemsFromIni(sheetCheckStr, out var sheetCheckItems))
            {
                var msg = $"Excelシートチェック項目が不正です。[キー:{KEY_EXCEL_SHEET_CHECK_ITEMS},値:{sheetCheckStr}]";
                LoggerUxer.Error(msg);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.ExcelSheelCheckItemInvalid;
                result.ResultMessage = msg;
                return result;
            }

            try
            {
                var weOrderList = new List<WeOrderDto>();

                // 1ファイル処理
                foreach (var file in files)
                {
                    var ret = RunOneFile(sectionName, file, now, sheetCheckItems, iniDic, startDate, weOrderList);
                    if (ret != (int)Common.RetCode.Success)
                    {
                        result.Succeeded = false;
                        result.ExitCode = ret;
                        return result;
                    }
                }

                if (weOrderList.Count == 0)
                {
                    var msg = $"対象データがありません。[{KEY_IN_FOLDER_PATH}:{inFolderPath},{KEY_FILE_NAME_PATTERN}={fileNamePattern}]";
                    LoggerUxer.Warn(msg);
                    result.Succeeded = true;
                    result.ResultMessage = msg;
                    // ワーニングでも成功
                    return result;
                }

                // ファイル名：未指定時はエラーとする
                var fileName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_CSV_FILE_NAME, string.Empty);
                if (string.IsNullOrEmpty(fileName))
                {
                    var msg = $"設定ファイルにCSVファイル名が指定されていません。[キー:{KEY_CSV_FILE_NAME}]";
                    LoggerUxer.Error(msg);
                    result.Succeeded = false;
                    result.ExitCode = (int)Common.RetCode.CsvFileNameNotSetting;
                    result.ResultMessage = msg;
                    return result;
                }

                if (fileName.IndexOf('.') == -1)
                {
                    // 拡張子がないとき、付与する
                    fileName += ".csv";
                    LoggerUxer.Info($"CSVファイル名に拡張子を付与しました。[ファイル名:{fileName}]");
                }

                // CSVファイルパス作成
                var csvPath = PathUxer.CreateFullPathEx(
                    DictionaryUxer.GetValueOrDefault(iniDic, KEY_OUT_FOLDER_PATH, string.Empty),
                    fileName,
                    DictionaryUxer.GetValueOrDefault(iniDic, KEY_FILE_DT_PREFIX, string.Empty),
                    DictionaryUxer.GetValueOrDefault(iniDic, KEY_FILE_DT_SUFFIX, string.Empty));
                LoggerUxer.Info($"[CSVファイルパス:{csvPath}]");

                // リストを圧縮（重複キーは排除）
                var maeCnt = weOrderList.Count;
                weOrderList = weOrderList
                    .GroupBy(x => new
                    {
                        x.LineName,
                        x.BasicPartsNo,
                        x.WorkDate
                    })
                    .Select(g => new WeOrderDto
                    {
                        LineId = g.Min(x => x.LineId),
                        LineName = g.Key.LineName,
                        BasicPartsNo = g.Key.BasicPartsNo,
                        WorkDate = g.Key.WorkDate,
                        CreateRecordDate = g.Min(x => x.CreateRecordDate),
                        AfAmount = g.Sum(x => x.AfAmount),
                        WeAmount = g.Sum(x => x.WeAmount),
                        KdWeAmount = g.Sum(x => x.KdWeAmount),
                        ProductionLur = g.Max(x => x.ProductionLur),
                        ModelCd = g.Min(x => x.ModelCd),
                        Derivation = g.Min(x => x.Derivation),
                        TactTime = g.Min(x => x.TactTime)
                    })
                    .ToList();

                var atoCnt = weOrderList.Count;
                LoggerUxer.Info($"再集計圧縮完了。[前:{maeCnt}件,後:{atoCnt}件]");

                // エンコーディング指定（オプション）
                var encodingTypeName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_CSV_ENCODING, "utf8");
                var encodingType = EncodingUxer.GetEncodingType(encodingTypeName);
                if (encodingType == null)
                {
                    encodingType = EncodingUxer.EncodingType.Utf8WithoutBom;
                    LoggerUxer.Warn($"CSVエンコーディングタイプ名が不正です。UFT-8で処理します。[キー:{KEY_CSV_ENCODING},値:{encodingTypeName}]");
                }

                // リストをCSV出力
                if (!CsvUxer.ExportToCsv(
                    weOrderList,
                    csvPath,
                    out messageForMe,
                    (EncodingUxer.EncodingType)encodingType))
                {
                    LoggerUxer.Error(messageForMe);
                    result.Succeeded = false;
                    result.ExitCode = (int)Common.RetCode.ExportToCsvError;
                    result.ResultMessage = messageForMe;
                    return result;
                }
                LoggerUxer.Info($"[出力件数:{weOrderList.Count}]");

                LoggerUxer.Info($"Excel→CSV機能（溶接加工計画）が終了しました。");

                result.Succeeded = true;
                return result;
            }
            catch (Exception ex)
            {
                var msg = $"エラーが発生しました。[Run(),エラー内容:{ex.Message}]";
                LoggerUxer.Error(msg);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.ExcelToCsvException;
                result.ResultMessage = msg;
                return result;
            }
        }

        /// <summary>
        /// ExcelToCsv機能（簡易版）
        /// </summary>
        /// <param name="iniFilePath">INIパス</param>
        /// <param name="sectionName">セクション名</param>
        /// <returns>結果DTO</returns>
        public FuncExcelToCsvWeOrderByIniResultDto TryFuncExcelToCsvEazyByIni(
            string iniFilePath,
            string sectionName)
        {
            // INIキー定義
            const string KEY_IN_FOLDER_PATH = "InFolderPath";
            const string KEY_FILE_NAME_PATTERN = "FileNamePattern";
            const string KEY_EXCEL_SHEET_CHECK_ITEMS = "ExcelSheetCheckItems";
            const string KEY_OUTPUT_TYPE = "OutputType";
            const string KEY_CSV_FILE_NAME = "CsvFileName";
            const string KEY_OUT_FOLDER_PATH = "OutFolderPath";
            const string KEY_FILE_DT_PREFIX = "FileDtPrefix";
            const string KEY_FILE_DT_SUFFIX = "FileDtSuffix";
            const string KEY_CSV_ENCODING = "CsvEncoding";
            const string KEY_KANI_TOKUSHU = "KaniTokushu";

            // 結果DTO
            var result = new FuncExcelToCsvWeOrderByIniResultDto();

            // ラインIDヘルパー
            LineIdHelper lineIdHelper = new LineIdHelper();

            // 現在日時
            DateTime now = DateTime.Now;

            LoggerUxer.Info($"Excel→CSV機能（簡易版）を開始します。");

            // INI取得
            var iniDic = IniReferenceResolveUxer.ReadSectionWithReference(
                iniFilePath, sectionName);

            // 開始日取得
            if (!CommonUxer.TryGetStartDate(iniDic, out var startDate, out var messageForMe))
            {
                var msg = $"開始日の取得に失敗しました。[エラー内容:{messageForMe}]";
                LoggerUxer.Error(msg);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.StartDateError;
                result.ResultMessage = msg;
                return result;
            }
            LoggerUxer.Info($"[開始日:{startDate:yyyy/MM/dd}]");

            // 終了日取得（指定ありのときのみ）
            if (!CommonUxer.TryGetEndDate(iniDic, startDate, out var endDate, out messageForMe))
            {
                var msg = $"終了日の取得に失敗しました。[エラー内容:{messageForMe}]";
                LoggerUxer.Error(msg);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.EndDateError;
                result.ResultMessage = msg;
                return result;
            }
            if (endDate is null)
            {
                LoggerUxer.Info($"[終了日:なし]");
            }
            else
            {
                LoggerUxer.Info($"[終了日:{((DateTime)endDate):yyyy/MM/dd}]");
            }

            // 入力フォルダパス
            var inFolderPath = DictionaryUxer.GetValueOrDefault(iniDic, KEY_IN_FOLDER_PATH, string.Empty);
            if (!CheckInFolder(inFolderPath, out messageForMe))
            {
                LoggerUxer.Error(messageForMe);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.InFolderPathError;
                result.ResultMessage = messageForMe;
                return result;
            }
            LoggerUxer.Info($"[入力フォルダ:{inFolderPath}]");

            // 対象ファイル群取得
            var fileNamePattern = DictionaryUxer.GetValueOrDefault(iniDic, KEY_FILE_NAME_PATTERN, "*");
            if (!GetFiles(
                iniDic,
                inFolderPath,
                fileNamePattern,
                out List<string> files,
                out messageForMe))
            {
                LoggerUxer.Error(messageForMe);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.TargetFilesGetError;
                result.ResultMessage = messageForMe;
                return result;
            }

            if (files.Count == 0)
            {
                var msg = $"対象ファイルがありません。[{KEY_IN_FOLDER_PATH}:{inFolderPath},{KEY_FILE_NAME_PATTERN}={fileNamePattern}]";
                LoggerUxer.Warn(msg);
                result.Succeeded = true;
                result.ResultMessage = msg;
                // ワーニングでも成功
                return result;
            }

            try
            {
                var lineList = new List<string>();

                // 1ファイル処理
                foreach (var file in files)
                {
                    // ライン判別とINI更新
                    if (!LineHanteiFromFilePathAndReloadIni(
                        file, iniDic, sectionName,
                        out var id, out var excelSettings, out messageForMe))
                    {
                        LoggerUxer.Error($"ライン判別とINI更新に失敗しました。[詳細:{messageForMe}]");
                        result.Succeeded = false;
                        result.ExitCode = (int)Common.RetCode.ExcelSettingsError;
                        return result;
                    }

                    // ラインID更新
                    lineIdHelper.FileLineId = id;

                    // アイテムと列レターのマッピング辞書作成
                    if (!TryCreateRetsuDic(iniDic, out var cdList, out messageForMe))
                    {
                        LoggerUxer.Error(messageForMe);
                        result.Succeeded = false;
                        result.ExitCode = (int)Common.RetCode.ExcelSettingsError;
                        return result;
                    }

                    // 特殊処理判定
                    var kaniTokushu = DictionaryUxer.GetValueOrDefault(iniDic, KEY_KANI_TOKUSHU, string.Empty);
                    LoggerUxer.Info($"[簡易特殊処理（{KEY_KANI_TOKUSHU}）:{(string.IsNullOrEmpty(kaniTokushu) ? "なし" : kaniTokushu)}]");
                    switch (kaniTokushu)
                    {
                        case "":
                            // 特殊なし：処理継続
                            break;
                        case "鋳造LPDC":
                            // 鋳造LPDC
                            if (!TryKaniChuzoLpdc(file, out var tokushuOutList, out messageForMe))
                            {
                                LoggerUxer.Error($"特殊処理（鋳造LPDC）に失敗しました。[詳細:{messageForMe}]");
                                result.Succeeded = false;
                                result.ExitCode = (int)Common.RetCode.KaniTokushuError;
                                return result;
                            }
                            lineList.AddRange(CreateFormattedTextAll(iniDic, cdList, tokushuOutList));
                            lineIdHelper.FileLineId = -1;
                            continue;
                        case "鋳造GDC":
                            // 鋳造GDC
                            if (!TryKaniChuzoGdc(file, out tokushuOutList, out messageForMe))
                            {
                                LoggerUxer.Error($"特殊処理（鋳造GDC）に失敗しました。[詳細:{messageForMe}]");
                                result.Succeeded = false;
                                result.ExitCode = (int)Common.RetCode.KaniTokushuError;
                                return result;
                            }
                            lineList.AddRange(CreateFormattedTextAll(iniDic, cdList, tokushuOutList));
                            lineIdHelper.FileLineId = -1;
                            continue;
                        default:
                            LoggerUxer.Error($"簡易特殊処理の指定が不正です。[{KEY_KANI_TOKUSHU}:{kaniTokushu}]");
                            result.Succeeded = false;
                            result.ExitCode = (int)Common.RetCode.KaniTokushuError;
                            return result;
                    }

                    // シートチェック項目取得
                    var sheetCheckStr = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_SHEET_CHECK_ITEMS, string.Empty);
                    LoggerUxer.Info($"[Excelシートチェック項目:{sheetCheckStr}]");
                    if (!TryGetExcelSheetCheckItemsFromIni(sheetCheckStr, out var sheetCheckItems))
                    {
                        var msg = $"Excelシートチェック項目が不正です。[キー:{KEY_EXCEL_SHEET_CHECK_ITEMS},値:{sheetCheckStr}]";
                        LoggerUxer.Error(msg);
                        result.Succeeded = false;
                        result.ExitCode = (int)Common.RetCode.ExcelSheelCheckItemInvalid;
                        result.ResultMessage = msg;
                        return result;
                    }

                    // 1ファイル処理
                    var outList = new List<string[]>();
                    var ret = RunOneFileEazy(
                        file, now, sheetCheckItems,
                        iniDic, excelSettings,
                        startDate, endDate,
                        lineIdHelper,
                        cdList, outList);
                    if (ret != (int)Common.RetCode.Success)
                    {
                        result.Succeeded = false;
                        result.ExitCode = ret;
                        return result;
                    }

                    // 出力タイプ
                    var type = DictionaryUxer.GetValueOrDefault(iniDic, KEY_OUTPUT_TYPE, "CSV");
                    switch (type)
                    {
                        case "FormattedText":
                            lineList.AddRange(CreateFormattedTextAll(iniDic, cdList, outList));
                            break;
                        default:
                            // CSV扱い
                            // ヘッダ
                            lineList.Add(
                                ColumnDefinition.GetCsvHeader(cdList));
                            // データ
                            lineList.AddRange(
                                outList.Select(
                                    row => string.Join(
                                        ",", row.Select(field => $"\"{field}\""))));
                            break;
                    }
                    lineIdHelper.FileLineId = -1;
                }

                if (lineList.Count == 0)
                {
                    var msg = $"対象データがありません。[{KEY_IN_FOLDER_PATH}:{inFolderPath},{KEY_FILE_NAME_PATTERN}={fileNamePattern}]";
                    LoggerUxer.Warn(msg);
                    result.Succeeded = true;
                    result.ResultMessage = msg;
                    // ワーニングでも成功
                    return result;
                }

                // ファイル名：未指定時はエラーとする
                var fileName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_CSV_FILE_NAME, string.Empty);
                if (string.IsNullOrEmpty(fileName))
                {
                    var msg = $"設定ファイルにCSVファイル名が指定されていません。[キー:{KEY_CSV_FILE_NAME}]";
                    LoggerUxer.Error(msg);
                    result.Succeeded = false;
                    result.ExitCode = (int)Common.RetCode.CsvFileNameNotSetting;
                    result.ResultMessage = msg;
                    return result;
                }

                if (fileName.IndexOf('.') == -1)
                {
                    // 拡張子がないとき、付与する
                    fileName += ".csv";
                    LoggerUxer.Info($"CSVファイル名に拡張子を付与しました。[ファイル名:{fileName}]");
                }

                // 出力ファイルパス作成
                var outputFilePath = PathUxer.CreateFullPathEx(
                    DictionaryUxer.GetValueOrDefault(iniDic, KEY_OUT_FOLDER_PATH, string.Empty),
                    fileName,
                    DictionaryUxer.GetValueOrDefault(iniDic, KEY_FILE_DT_PREFIX, string.Empty),
                    DictionaryUxer.GetValueOrDefault(iniDic, KEY_FILE_DT_SUFFIX, string.Empty));
                LoggerUxer.Info($"[CSVファイルパス:{outputFilePath}]");

                // エンコーディング指定（オプション）
                var encodingTypeName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_CSV_ENCODING, "utf8");
                var encodingType = EncodingUxer.GetEncodingType(encodingTypeName);
                if (encodingType == null)
                {
                    encodingType = EncodingUxer.EncodingType.Utf8WithoutBom;
                    LoggerUxer.Warn($"CSVエンコーディングタイプ名が不正です。UFT-8で処理します。[キー:{KEY_CSV_ENCODING},値:{encodingTypeName}]");
                }

                // 出力
                System.IO.File.WriteAllLines(
                    outputFilePath, lineList, 
                    EncodingUxer.GetEncoding(
                        (EncodingUxer.EncodingType)encodingType));

                LoggerUxer.Info($"[出力件数:{lineList.Count}]");

                LoggerUxer.Info($"Excel→CSV機能（簡易版）が終了しました。");

                result.Succeeded = true;
                return result;
            }
            catch (Exception ex)
            {
                var msg = $"エラーが発生しました。[Run(),エラー内容:{ex.Message}]";
                LoggerUxer.Error(msg);
                result.Succeeded = false;
                result.ExitCode = (int)Common.RetCode.ExcelToCsvException;
                result.ResultMessage = msg;
                return result;
            }
        }

        /// <summary>
        /// 簡易処理（鋳造LPDC）
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="iniDic"></param>
        /// <param name="outputList"></param>
        /// <param name="resultMessage"></param>
        /// <returns></returns>
        private static bool TryKaniChuzoLpdc(
            string filePath,
            out List<string[]> outList,
            out string resultMessage)
        {
            const string SHEET_NAME_JISSEKI_NYURYOKU = "実績入力";
            const string SHEET_NAME_CHUZO_IN = "鋳造IN";

            outList = new List<string[]>();
            resultMessage = string.Empty;

            try
            {
                // 開く
                using (var book = LoadWorkbookWithoutPhoneticError(filePath))
                {
                    // シート有無CK
                    if (!book.Worksheets.Any(m => m.Name == SHEET_NAME_JISSEKI_NYURYOKU))
                    {
                        resultMessage = $"「実績入力」シートがありません。[ファイル:{filePath}]";
                        return false;
                    }
                    if (!book.Worksheets.Any(m => m.Name == SHEET_NAME_CHUZO_IN))
                    {
                        resultMessage = $"「鋳造IN」シートがありません。[ファイル:{filePath}]";
                        return false;
                    }

                    // 実績入力IN
                    List<KaniChuzoLpdcJissekiNyuryokuDto> jnList = new List<KaniChuzoLpdcJissekiNyuryokuDto>();
                    var sheet = book.Worksheet(SHEET_NAME_JISSEKI_NYURYOKU);
                    for (var row = 12; ; row++)
                    {
                        if (string.IsNullOrEmpty(sheet.Cell(row, 2).GetFormattedString().Trim()))
                        {
                            break;
                        }
                        jnList.Add(new KaniChuzoLpdcJissekiNyuryokuDto()
                        {
                            Date = DateTime.TryParse(sheet.Cell(row, 2).GetFormattedString().Trim(), out var dat) ? dat : DateTime.MinValue,
                            Kishu = sheet.Cell(row, 3).GetFormattedString().Trim(),
                            Suryo = int.TryParse(sheet.Cell(row, 4).GetFormattedString().Trim(), out var intv) ? intv : int.MinValue,
                            Furyo = int.TryParse(sheet.Cell(row, 5).GetFormattedString().Trim(), out intv) ? intv : int.MinValue,
                            Hensu = sheet.Cell(row, 6).GetFormattedString().Trim(),
                        });
                    }

                    // 鋳造ININ
                    List<KaniChuzoLpdcChuzoInDto> ciList = new List<KaniChuzoLpdcChuzoInDto>();
                    sheet = book.Worksheet(SHEET_NAME_CHUZO_IN);
                    var yearText = XlUxer.GCell(sheet, 2, 3).GetFormattedString().Replace("年", "").Trim();
                    var year = int.TryParse(yearText, out var intVal) ? intVal : 1900;
                    var monthText = XlUxer.GCell(sheet, 2, 5).GetFormattedString().Replace("月", "").Trim();
                    var month = int.TryParse(monthText, out intVal) ? intVal : 1;
                    var karaCnt = 0;
                    for (var row = 7; ; row++)
                    {
                        if (string.IsNullOrEmpty(sheet.Cell(row, 2).GetFormattedString().Trim()))
                        {
                            karaCnt++;
                            if (karaCnt >= 10)
                            {
                                break;
                            }
                            continue;
                        }
                        karaCnt = 0;
                        for (var col = 5; ; col++)
                        {
                            var dayText = sheet.Cell(6, col).GetFormattedString().Trim();
                            if (string.IsNullOrEmpty(dayText)) break;
                            if (dayText == "ＴＬＬ") break;
                            if (!int.TryParse(dayText, out var dayInt)) break;

                            var text = sheet.Cell(row, col).GetFormattedString().Trim();
                            if (string.IsNullOrEmpty(text)) continue;
                            if (text == "0") continue;

                            var su = int.TryParse(text, out intVal) ? intVal : int.MinValue;
                            ciList.Add(new KaniChuzoLpdcChuzoInDto()
                            {
                                Kishu = sheet.Cell(row, 2).GetFormattedString().Trim(),
                                Buban = sheet.Cell(row, 3).GetFormattedString().Trim(),
                                Kubun = sheet.Cell(row, 4).GetFormattedString().Trim(),
                                Date = new DateTime(year, month, dayInt),
                                Su = su,
                            });
                        }
                    }

                    // 【残】jnListとciListをマージして、outListに出力する

                    //var jn1 = jnList
                    //    .Select(m => new { m.Kishu, m.Date, m.Suryo })
                    //    .OrderBy(m => m.Kishu)
                    //    .ThenBy(m => m.Date)
                    //    .ToList();
                    //foreach (var dto in jnList)
                    //    System.Diagnostics.Debug.WriteLine(
                    //        $"{dto.Kishu}\t{dto.Date}\t{dto.Suryo}");
                    //var ci1 = ciList
                    //    .GroupBy(m=>new {m.Kishu,m.Date})
                    //    .Select(m => new { m.Key.Kishu, m.Key.Date, Total = m.Sum(n=>n.Su), })
                    //    .OrderBy(m => m.Kishu)
                    //    .ThenBy(m => m.Date)
                    //    .ToList();
                    //foreach (var dto in ciList)
                    //    System.Diagnostics.Debug.WriteLine(
                    //        $"{dto.Kishu}\t{dto.Date}\t{dto.Su}");
                }

            }
            catch (Exception ex)
            {
                LoggerUxer.Error($"特殊処理（鋳造LPDC）にてエラーが発生しました。[{ex.Message}]");
                return false;
            }

            return true;
        }

        /// <summary>
        /// 簡易処理（鋳造GDC）
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="outList"></param>
        /// <param name="resultMessage"></param>
        /// <returns></returns>
        private static bool TryKaniChuzoGdc(
            string filePath,
            out List<string[]> outList,
            out string resultMessage)
        {
            const string SHEET_NAME_BEFORE = "*月*鋳造計画";
            const string SHEET_NAME_AFTER = "払出し";

            outList = new List<string[]>();
            resultMessage = string.Empty;

            try
            {
                // 開く
                using (var book = LoadWorkbookWithoutPhoneticError(filePath))
                {
                    LoggerUxer.Info($"[対象ファイル:{filePath}]");
                    var targetIn = false;
                    foreach (var sheet in book.Worksheets)
                    {
                        if (StringUxer.WildcardMatch(sheet.Name, SHEET_NAME_BEFORE))
                        {
                            // 対象シート開始前検知（次のシートから対象にする）
                            targetIn = true;
                            continue;
                        }
                        if (StringUxer.WildcardMatch(sheet.Name, SHEET_NAME_AFTER))
                        {
                            // 対象シート終了後検知（シート処理終わり）
                            break;
                        }
                        // 対象シート中でないとき飛ばす
                        if (!targetIn) continue;

                        // 一応シートのチェック
                        if (sheet.Cell("E2").GetFormattedString().Trim()!= "鋳造ＭＯ" ||
                            sheet.Cell("E3").GetFormattedString().Trim() != "ＧＤＣ" ||
                            sheet.Cell("K3").GetFormattedString().Trim() != "月生産管理")
                        {
                            resultMessage = $"シート内容が不正です。[ファイル:{filePath},シート:{sheet.Name}]";
                            return false;
                        }
                        LoggerUxer.Info($"[対象シート:{sheet.Name}]");

                        var kugyoCnt = 0;
                        for (var row = 7; row < 1000; row++)
                        {
                            var a = XlUxer.GCell(sheet, "A" + row).GetFormattedString().Trim();
                            var b = XlUxer.GCell(sheet, "B" + row).GetFormattedString().Trim();
                            var c = XlUxer.GCell(sheet, "C" + row).GetFormattedString().Trim();
                            var d = XlUxer.GCell(sheet, "D" + row).GetFormattedString().Trim();
                            var f = XlUxer.GCell(sheet, "F" + row).GetFormattedString().Trim();

                            if (string.IsNullOrEmpty(b))
                            {
                                kugyoCnt++;
                                if (kugyoCnt >= 10) break;
                                continue;
                            }

                            int[] su = new int[31];
                            for (var col = 8; col < 8 + 31; col++)
                            {
                                var strVal = XlUxer.GCell(sheet, row, col).GetFormattedString().Trim();
                                if (string.IsNullOrEmpty(strVal)) continue;

                                int intVal = int.TryParse(strVal, out var intV) ? intV : int.MinValue;

                                aaaaaaa


                            }
                        }


                    }



                }
            }
            catch (Exception ex)
            {
                LoggerUxer.Error($"特殊処理（鋳造LPDC）にてエラーが発生しました。[{ex.Message}]");
                return false;
            }

            return true;
        }

        /// <summary>
        /// 書式付きテキスト作成
        /// </summary>
        /// <param name="iniDic"></param>
        /// <param name="cdList"></param>
        /// <param name="outList"></param>
        /// <returns></returns>
        private static List<string> CreateFormattedTextAll(
            Dictionary<string,string> iniDic,
            List<ColumnDefinition> cdList,
            List<string[]> outList)
        {
            const string INI_KEY_FORMATTED_TEXT_FORMAT_FILE = "FormattedTextFormatFile";

            var ftff = DictionaryUxer.GetValueOrDefault(iniDic, INI_KEY_FORMATTED_TEXT_FORMAT_FILE, string.Empty);
            if (string.IsNullOrEmpty(ftff))
                return new List<string>() { $"[ERROR]INI未設定です。[キー:{INI_KEY_FORMATTED_TEXT_FORMAT_FILE}]" };

            var patterns = ftff.Split('|');

            List<FormattedTextPatternDto> ftffList = new List<FormattedTextPatternDto>();
            foreach (var pattern in patterns)
            {
                var items = pattern.Split(',');

                var dto = new FormattedTextPatternDto();

                if (items.Length == 1)
                {
                    dto.FileName = items[0].Trim();
                }
                else if (items.Length == 3)
                {
                    dto.ColumnName = items[0].Trim();
                    dto.JudgeValue = items[1].Trim();
                    dto.FileName = items[2].Trim();
                }
                else
                {
                    dto.Format = $"書式つきテキストパターン定義に不備があります。[キー:{INI_KEY_FORMATTED_TEXT_FORMAT_FILE},値:{pattern}]";
                }
                if (!string.IsNullOrEmpty(dto.FileName))
                {
                    if (dto.FileName.IndexOf("\\") < 0)
                        dto.FileName = ".\\" + dto.FileName;

                    if (!File.Exists(dto.FileName))
                    {
                        dto.Format = $"書式ファイルがありません。[キー:{INI_KEY_FORMATTED_TEXT_FORMAT_FILE},ファイル名:{dto.FileName}]";
                    }
                    else
                    {
                        try
                        {
                            using (var sr = new StreamReader(dto.FileName))
                            {
                                dto.Format = sr.ReadToEnd();
                            }
                        }
                        catch (Exception ex)
                        {
                            dto.Format = $"ファイルの読み込みに失敗しました。[パス:{dto.FileName},詳細:{ex.Message}]";
                        }
                    }
                }
                ftffList.Add(dto);
            }
            List<string> list = new List<string>();

            // 行
            foreach (var items in outList)
            {
                if (items.Length != cdList.Count)
                {
                    list.Add($"列数が合いません。[列定義数:{cdList.Count},行データ:{string.Join(",",items)}]");
                    continue;
                }
                var format = string.Empty;
                foreach (var dto in ftffList)
                {
                    if (string.IsNullOrEmpty(dto.ColumnName) ||
                        string.IsNullOrEmpty(dto.JudgeValue))
                    {
                        // その他：即時これを適用
                        format = dto.Format;
                        break;
                    }
                    else
                    {
                        var cd = cdList.FirstOrDefault(m => m.LogicalName == dto.ColumnName);
                        if (cd is null) continue;
                        var index = cdList.IndexOf(cd);
                        switch(dto.JudgeValue)
                        {
                            case "@@@EMPTY@@@":
                                if (string.IsNullOrEmpty(items[index]))
                                {
                                    format = dto.Format;
                                    break;
                                }
                                break;
                            case "@@@NOT_EMPTY@@@":
                                if (!string.IsNullOrEmpty(items[index]))
                                {
                                    format = dto.Format;
                                    break;
                                }
                                break;
                            default:
                                if (items[index] == dto.JudgeValue)
                                {
                                    format = dto.Format;
                                    break;
                                }
                                break;
                        }
                        if (!string.IsNullOrEmpty(format)) break;
                    }
                }
                // 項目値で置換し出力レコードを作成
                var line = format;
                for (var index2 = 0; index2 < items.Length; index2++)
                {
                    line = line.Replace($"@@@{cdList[index2].LogicalName}@@@", items[index2]);
                }
                list.Add(line);
            }
            return list;
        }

        /// <summary>
        /// DTOでExcelColumnLetterAttribute属性がついたプロパティの辞書作成
        /// </summary>
        /// <returns>作成した辞書</returns>
        private static Dictionary<string, string> CreateExcelColumnDtoPropDic()
        {
            // 属性付きのプロパティのみ抽出
            var list = typeof(WeOrderDto).GetProperties()
                .Select(p => new
                {
                    p.Name,
                    ExcelLetter = p.GetCustomAttribute<ExcelColumnLetterAttribute>()?.ColumnLetter
                })
                .Where(p => p.ExcelLetter != null)
                .ToList();

            // 辞書化
            Dictionary<string, string> dic = new Dictionary<string, string>();
            foreach (var item in list)
            {
                dic.Add(item.Name, item.ExcelLetter);
            }
            return dic;
        }

        private static bool TryCreateRetsuDic(Dictionary<string, string> iniDic, out List<ColumnDefinition> cdList, out string resultMessage)
        {
            cdList = new List<ColumnDefinition>();
            resultMessage = string.Empty;

            for (var cnt = 0; cnt < 1000; cnt++)
            {
                var key = $"Col{cnt:d3}";

                var iniValue = DictionaryUxer.GetValueOrDefault(
                    iniDic, key, string.Empty);

                if (string.IsNullOrEmpty(iniValue)) continue;

                var dto = ColumnDefinition.Parse(iniValue, out var messageForMe);
                if (dto is null)
                {
                    resultMessage = $"列定義に不備があります。[キー:{key},設定値:{iniValue},エラー詳細:{messageForMe}]";
                    return false;
                }
                cdList.Add(dto);
            }
            return true;
        }

        // INIキー定義
        private const string KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING = "ExcelItemNameAndColumnLetterMapping";

        /// <summary>
        /// DTOアイテムの列レターマッピング辞書作成
        /// </summary>
        /// <param name="iniDic">INIDIC</param>
        /// <param name="dic">[OUT]作成した辞書</param>
        /// <param name="resultMessage">[OUT]結果メッセージ</param>
        /// <returns>成否</returns>
        private bool TryCreateItemColumnMappingDic(Dictionary<string, string>iniDic, out Dictionary<string, string> dic, out string resultMessage)
        {
            resultMessage = string.Empty;

            // Excel列レター属性を有するDTOプロパティ名のリスト
            dic = CreateExcelColumnDtoPropDic();
            LoggerUxer.Info($"[Excel列レター属性を有するアイテム名のリスト:{string.Join(",", dic.Keys)}]");
            LoggerUxer.Info($"[Excel列レター属性を有するアイテムの列レターのリスト:{string.Join(",", dic.Values)}]");

            // INIの拡張定義を取得
            var str = DictionaryUxer.GetValueOrDefault(
                iniDic,
                KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING,
                string.Empty);

            // INIに拡張定義がないとき、デフォルトのみなので終了
            if (string.IsNullOrEmpty(str)) return true;

            var maps = str.Split('|');
            foreach (var map in maps)
            {
                var kvp = map.Split(':');
                if (kvp.Length != 2)
                {
                    resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で「アイテム名:列レター」書式が不正です。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},値:{str}]";
                    LoggerUxer.Error(resultMessage);
                    return false;
                }

                // アイテム名のバリデ
                if (!dic.Keys.Contains(kvp[0]))
                {
                    // エラー時（アイテム名誤りまたは指定アイテムにExcel列レター属性が無い）
                    resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で「アイテム名」が対象のアイテム名に含まれません。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},値:{str},アイテム名:{kvp[0]}]";
                    LoggerUxer.Error(resultMessage);
                    LoggerUxer.Info($"[有効なアイテム名:{string.Join(",", dic.Keys)}]");
                    return false;
                }

                //kvp[1] = kvp[1].ToUpper();

                if (kvp[1].IndexOf(",") != -1)
                {
                    // Excel列以外の指定（別のところから取得）の場合
                    var strSplit = kvp[1].Split(',');
                    // 漁法名
                    switch(strSplit[0])
                    {
                        case "別シート":
                            if (strSplit.Length != 5)
                            {
                                resultMessage = $"設定ファイルのアイテム-列レターマッピング指定でCSV要素数が違います。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},漁法名:{strSplit[0]}]";
                                LoggerUxer.Error(resultMessage);
                                return false;
                            }
                            if (!ExcelCellValidatorUxer.IsValidA1Format(strSplit[2] + "1"))
                            {
                                resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で列レターが不正です。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},漁法名:{strSplit[0]},列レター:{strSplit[2]}]";
                                LoggerUxer.Error(resultMessage);
                                return false;
                            }
                            if (!ExcelCellValidatorUxer.IsValidA1Format(strSplit[3] + "1"))
                            {
                                resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で列レターが不正です。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},漁法名:{strSplit[0]},列レター:{strSplit[3]}]";
                                LoggerUxer.Error(resultMessage);
                                return false;
                            }
                            if (!int.TryParse(strSplit[4], out var intVal) || intVal < 1)
                            {
                                resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で開始行番号が不正です。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},漁法名:{strSplit[0]},開始行番号:{strSplit[4]}]";
                                LoggerUxer.Error(resultMessage);
                                return false;
                            }
                            break;
                        default:
                            resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で「漁法名」が未存在です。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},漁法名:{strSplit[0]}]";
                            LoggerUxer.Error(resultMessage);
                            return false;
                    }
                }
                // Excel列レターのバリデ
                else if (!XlUxer.TryConvertColLetterToNumber(kvp[1].ToUpper(), out var colNo))
                {
                    // バリデエラー
                    resultMessage = $"設定ファイルのアイテム-列レターマッピング指定で「列レター」書式が不正です。[キー:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING},列レター値:{kvp[1]}]";
                    LoggerUxer.Error(resultMessage);
                    return false;
                }

                // 辞書を更新
                dic[kvp[0]] = kvp[1];
            }
            return true;
        }

        /// <summary>
        /// 入力フォルダパスのチェック
        /// </summary>
        /// <param name="folderPath">入力フォルダパス</param>
        /// <param name="resultMessage">メッセージ</param>
        /// <returns>成否</returns>
        private static bool CheckInFolder(string folderPath, out string resultMessage)
        {
            resultMessage = string.Empty;
            if (string.IsNullOrEmpty(folderPath))
            {
                resultMessage = $"設定ファイルに入力フォルダパスが設定されていません。";
                return false;
            }
            if (!Directory.Exists(folderPath))
            {
                resultMessage = $"設定ファイルの入力フォルダパスのフォルダが存在しません。[入力フォルダパス:{folderPath}]";
                return false;
            }
            return true;
        }

        // INIキー定義
        private const string KEY_EXCEL_INPUT_FILE_PATTERN_ONLY_NEWEST = "ExcelInputFilePatternOnlyNewest";

        /// <summary>
        /// 対象ファイル群（ファイル名パターン合致）のファイルパスリスト取得
        /// </summary>
        /// <param name="iniDic">INI辞書</param>
        /// <param name="inFolderPath">入力フォルダパス</param>
        /// <param name="fileNamePattern">入力ファイル名（パターン）</param>
        /// <param name="files">[出力]対象ファイル群</param>
        /// <param name="resultMessage">[出力]メッセージ</param>
        /// <returns>成否</returns>
        private static bool GetFiles(
            Dictionary<string, string> iniDic,
            string inFolderPath,
            string fileNamePattern,
            out List<string> files,
            out string resultMessage)
        {
            resultMessage = string.Empty;

            // パターン一致ファイル群中、最新の1件のみか？
            var newestOnly = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_INPUT_FILE_PATTERN_ONLY_NEWEST, string.Empty) == "1";

            string[] paths = null;

            if (newestOnly)
            {
                // パターン一致ファイル群中の更新日時が最新のもの１つだけ
                string latestFile = Directory.GetFiles(inFolderPath, fileNamePattern)
                    .OrderByDescending(f => File.GetLastWriteTime(f))
                    .FirstOrDefault();
                if (string.IsNullOrEmpty(latestFile))
                {
                    // 該当なしの時、空配列
                    paths = new string[0];
                }
                else
                {
                    // 該当ありの時、1個のみ
                    paths = new string[] { latestFile };
                }
            }
            else
            {
                paths = Directory.GetFiles(inFolderPath, fileNamePattern);
            }

            files = paths.ToList();
            return true;
        }

        /// <summary>
        /// 1ファイル実行
        /// </summary>
        /// <param name="path">Excelファイルパス</param>
        /// <param name="checkItems">シートチェック項目リスト</param>
        /// <param name="iniDic">INIディクショナリ</param>
        /// <param name="fromDate">開始日</param>
        /// <returns>0:OK、-1:NG</returns>
        private static int RunOneFile(
            string sectionName,
            string path,
            DateTime now,
            List<ExcelSheetCheckDto> checkItems,
            Dictionary<string,string> iniDic,
            DateTime fromDate,
            List<WeOrderDto> weOrderList)
        {
            // INIキー定義
            const string KEY_INCLUDED_SHEET_NAMES = "IncludedSheetNames";
            const string KEY_EXCLUDED_SHEET_NAMES = "ExcludedSheetNames";
            const string KEY_INCLUDED_LINE_NAMES = "IncludedLineNames";
            const string KEY_EXCLUDED_LINE_NAMES = "ExcludedLineNames";
            const string KEY_EXCEL_SKIP_SAME_MONTH_SHEETS = "ExcelSkipSameMonthSheets";

            if (System.IO.Path.GetExtension(path).ToLower() != ".xlsx" &&
                System.IO.Path.GetExtension(path).ToLower() != ".xlsm")
            {
                LoggerUxer.Warn($"拡張子が異なるためスキップします。[{path}]");
                return (int)Common.RetCode.Success;
            }

            LoggerUxer.Info($"[ファイル:{path}]");
            
            try
            {
                using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                using (var book = new XLWorkbook(fs))
                {
                    // 処理対象のシート名
                    var incSheetsCsv = DictionaryUxer.GetValueOrDefault(iniDic, KEY_INCLUDED_SHEET_NAMES, string.Empty);
                    if (!string.IsNullOrEmpty(incSheetsCsv))
                    {
                        LoggerUxer.Info($"[処理対象シート名:{incSheetsCsv}]");
                    }
                    else
                    {
                        LoggerUxer.Info($"[処理対象シート名:なし]");
                    }

                    // 処理対象外のシート名
                    var excSheetsCsv = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCLUDED_SHEET_NAMES, string.Empty);
                    if (!string.IsNullOrEmpty(excSheetsCsv))
                    {
                        if (string.IsNullOrEmpty(incSheetsCsv))
                        {
                            LoggerUxer.Info($"[処理対象外シート名:{excSheetsCsv}]");
                        }
                        else
                        {
                            LoggerUxer.Warn($"処理対象シート名が指定されているため、処理対象外指定は無効になります。[キー:{KEY_EXCLUDED_SHEET_NAMES},値:{excSheetsCsv}]");
                        }
                    }
                    else
                    {
                        LoggerUxer.Info($"[処理対象外シート名:なし]");
                    }
                    // シートフィルタ作成
                    var incSheets = string.IsNullOrEmpty(incSheetsCsv) ? new string[] { } : incSheetsCsv.Split(',');
                    var excSheets = string.IsNullOrEmpty(excSheetsCsv) ? new string[] { } : excSheetsCsv.Split(',');
                    var sheetFilter = new SheetFilterUxer(incSheets, excSheets);

                    // 処理対象のライン名
                    var incLinesCsv = DictionaryUxer.GetValueOrDefault(iniDic, KEY_INCLUDED_LINE_NAMES, string.Empty);
                    if (!string.IsNullOrEmpty(incLinesCsv))
                    {
                        LoggerUxer.Info($"[処理対象ライン名:{incLinesCsv}]");
                    }
                    else
                    {
                        LoggerUxer.Info($"[処理対象ライン名:なし]");
                    }

                    // 処理対象外のライン名
                    var excLinesCsv = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCLUDED_LINE_NAMES, string.Empty);
                    if (!string.IsNullOrEmpty(excLinesCsv))
                    {
                        if (string.IsNullOrEmpty(incLinesCsv))
                        {
                            LoggerUxer.Info($"[処理対象外ライン名:{excLinesCsv}]");
                        }
                        else
                        {
                            LoggerUxer.Warn($"処理対象ライン名が指定されているため、処理対象外指定は無効になります。[キー:{KEY_EXCLUDED_LINE_NAMES},値:{excLinesCsv}]");
                        }
                    }
                    else
                    {
                        LoggerUxer.Info($"[処理対象外ライン名:なし]");
                    }
                    // ラインフィルタ作成
                    var incLines = string.IsNullOrEmpty(incLinesCsv) ? new string[] { } : incLinesCsv.Split(',');
                    for (int cnt = 0; cnt < incLines.Count(); cnt++) incLines[cnt] = incLines[cnt].Replace("@@@COMMA@@@", ",");
                    var excLines = string.IsNullOrEmpty(excLinesCsv) ? new string[] { } : excLinesCsv.Split(',');
                    for (int cnt = 0; cnt < excLines.Count(); cnt++) excLines[cnt] = excLines[cnt].Replace("@@@COMMA@@@", ",");
                    var lineFilter = new LineFilterUxer(incLines, excLines);

                    // 同月シートスキップ
                    var skipSameMonthSheets = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_SKIP_SAME_MONTH_SHEETS, string.Empty);
                    if (string.IsNullOrEmpty(skipSameMonthSheets))
                    {
                        LoggerUxer.Error($"設定ファイルの同月シートスキップ有無が未指定です。[キー:{KEY_EXCEL_SKIP_SAME_MONTH_SHEETS}]");
                        return (int)Common.RetCode.ConfigError;
                    }
                    if (!bool.TryParse(skipSameMonthSheets.ToLower(),out var isSkipSameMonth))
                    {
                        LoggerUxer.Error($"設定ファイルの同月シートスキップ有無が不正値です。[{KEY_EXCEL_SKIP_SAME_MONTH_SHEETS}:{skipSameMonthSheets}]");
                        return (int)Common.RetCode.ConfigError;
                    }
                    LoggerUxer.Info($"[同月シートスキップ有無:{isSkipSameMonth.ToString().ToLower()}]");

                    // シートの年月保持
                    var yyyymmList = new List<string>();

                    // シート右端から左へループ
                    for (int sheetIndex = book.Worksheets.Count; sheetIndex >= 1; sheetIndex--)
                    {
                        // シート取得
                        var sheet = book.Worksheet(sheetIndex);

                        if (!sheetFilter.CanPass(sheet.Name.Trim()))
                        {
                            // シート名フィルタに引っかからなかった時、飛ばす
                            LoggerUxer.Info($"対象外のシートをスキップします。[シート名:{sheet.Name}]");
                            continue;
                        }

                        // 条件を満たさないシートは飛ばす
                        if (CheckExcelSheet(sheet, checkItems) == false)
                        {
                            LoggerUxer.Info($"シート条件を満たさないためスキップします。[シート名:{sheet.Name}]");
                            continue;
                        }

                        LoggerUxer.Info($"[シート名:{sheet.Name}]");

                        // ライン（先頭のみ）情報取得
                        if (!TryCreateLineInfoList(sheet, iniDic, lineFilter, out var lineInfoList, true))
                        {
                            return (int)Common.RetCode.ExcelLinesInfoGetError;
                        }

                        var lineNames = lineInfoList.Select(x => x.LineName).Distinct();
                        if (!lineFilter.HasAnyPass(lineNames))
                        {
                            LoggerUxer.Info($"シート内に対象のラインがないためスキップします。");
                            continue;
                        }

                        // 日付情報取得
                        if (!GetDateList(sheet, fromDate, null, iniDic, out var dateDic))
                        {
                            return (int)Common.RetCode.ExcelDateCellGetError;
                        }

                        if (isSkipSameMonth)
                        {
                            var yyyymm = dateDic.Values.Max().ToString("yyyyMM");
                            if (yyyymmList.Contains(yyyymm))
                            {
                                LoggerUxer.Info($"シートの年月が既に処理済みのため、このシートをスキップします。[シート名:{sheet.Name.Trim()},年月:{yyyymm.Substring(0, 4) + "年" + yyyymm.Substring(4, 2) + "月"}]");
                                continue;
                            }
                            yyyymmList.Add(yyyymm);
                        }

                        // 最大日が開始日より小さい場合、シートを飛ばす
                        if (dateDic.Values.Max() < fromDate)
                        {
                            LoggerUxer.Info($"シート最大日が開始日より前のためスキップします。[シート最大日:{(DateTime)(dateDic.Values.Max()):yyyy/MM/dd}]");
                            continue;
                        }

                        // ライン繰り返し
                        foreach (var line in lineInfoList)
                        {
                            // データ取得
                            if (!GetData(sheet, line, now, dateDic, fromDate, iniDic, weOrderList))
                            {
                                return (int)Common.RetCode.ExcelDataGetError;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LoggerUxer.Error($"1ファイル処理にてエラーが発生しました。[{ex.Message}]");
                return (int)Common.RetCode.ExcelOneFileException;
            }
            return (int)Common.RetCode.Success;
        }

        /// <summary>
        /// Excel対象シートチェック項目をINIファイルより取得
        /// </summary>
        /// <param name="checkItems">INIのチェック項目文字列</param>
        /// <returns>チェック項目のリスト</returns>
        private static bool TryGetExcelSheetCheckItemsFromIni(string checkItems, out List<ExcelSheetCheckDto> resultList)
        {
            resultList = new List<ExcelSheetCheckDto>();

            if (string.IsNullOrEmpty(checkItems)) return true;

            var items = checkItems.Split('|');

            foreach (var item in items)
            {
                var strs = item.Split(',');
                if (strs.Length != 2)
                {
                    LoggerUxer.Error($"Excel対象シートチェック項目にカンマの数が不正な箇所がありました。[全条件文字列:{checkItems},カンマ数:{strs.Length}]");
                    return false;
                }

                // A1形式セルアドレス（複数あり）
                var a1AddressList = strs[0]
                    .Split(new string[] { ExcelSheetCheckDto.OR_DELIMITER }, StringSplitOptions.None)
                    .ToList();
                for (var cnt = 0; cnt < a1AddressList.Count; cnt++)
                {
                    if (!XlUxer.TryValidateA1CellAddress(a1AddressList[cnt], out var resultAddress))
                    {
                        LoggerUxer.Error($"A1形式アドレス値が不正です。[アドレス値:{a1AddressList[cnt]},チェック項目文字列:{checkItems}]");
                        return false;
                    }
                    a1AddressList[cnt] = resultAddress;
                }

                // チェック値（複数あり）
                var checkValuesList = strs[1]
                    .Split(new string[] { ExcelSheetCheckDto.OR_DELIMITER }, StringSplitOptions.None)
                    .ToList();

                resultList.Add(new ExcelSheetCheckDto()
                {
                    a1AddressList = a1AddressList,
                    CheckValueList = checkValuesList,
                });
            }
            return true;
        }

        /// <summary>
        /// Excelの対象シートチェック
        /// </summary>
        /// <param name="sheet">シート</param>
        /// <param name="checkItems">チェック項目リスト</param>
        /// <returns>true:OK、false:NG</returns>
        private static bool CheckExcelSheet(IXLWorksheet sheet, List<ExcelSheetCheckDto> checkItems)
        {
            // チェック項目（複数）が全てOKであること
            foreach (var item in checkItems)
            {
                // セル（複数）とチェック値（複数）の組み合わせで1つでも一致すればOK
                var isMatch = false;
                foreach (var a1 in item.a1AddressList)
                {
                    for (var cnt = 0; cnt < item.CheckValueList.Count; cnt++)
                    {
                        if (!KanaConvertUxer.TryConvertHankakuToZenkaku(item.CheckValueList[cnt], out var resultForMe, out var messageForMe))
                        {
                            LoggerUxer.Error(messageForMe);
                            return false;
                        }
                        var checkValue = resultForMe;

                        if (!KanaConvertUxer.TryConvertHankakuToZenkaku(XlUxer.GCell(sheet, a1).GetFormattedString(), out resultForMe, out messageForMe))
                        {
                            LoggerUxer.Error(messageForMe);
                            return false;
                        }
                        var cellValue = resultForMe;

                        if (StringUxer.WildcardMatch(cellValue, checkValue))
                        {
                            isMatch = true;
                            break;
                        }
                    }
                    if (isMatch) break;
                }
                if (!isMatch)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 日付情報取得
        /// </summary>
        /// <param name="sheet">シート</param>
        /// <param name="iniDic">INIDIC</param>
        /// <param name="dateDic">日付辞書（キーは列番号）</param>
        /// <returns>成否</returns>
        private static bool GetDateList(
            IXLWorksheet sheet,
            DateTime fromDate,
            DateTime? toDate,
            Dictionary<string, string> iniDic,
            out Dictionary<int, DateTime> dateDic)
        {
            // INIキー定義
            const string KEY_EXCEL_DATE_ROW_MODE = "ExcelDateRowMode";
            const string EXCEL_DATE_ROW_MODE_FIXED = "Fixed";
            const string EXCEL_DATE_ROW_MODE_JUDGE = "Judge";
            const string EXCEL_DATE_ROW_MODE_NONE = "None";
            const string KEY_EXCEL_DATE_ROW = "ExcelDateRow";
            const string KEY_EXCEL_DATE_ROW_JUDGE_COL = "ExcelDateRowJudgeCol";
            const string KEY_EXCEL_DATE_ROW_JUDGE_VALUE = "ExcelDateRowJudgeValue";
            const string KEY_EXCEL_DATE_ROW_START_COL = "ExcelDateRowStartCol";
            const string KEY_EXCEL_DATE_ROW_END_VALUE = "ExcelDateRowEndValue";
            const string KEY_EXCEL_DATE_YM_GET_MODE = "ExcelDateYMGetMode";
            const string KEY_EXCEL_NEN_GETSU_CELL_ADDRESS = "ExcelNenGetsuCellAddress";
            const string KEY_EXCEL_DATE_YEAR_CELL = "ExcelDateYearCell";
            const string KEY_EXCEL_DATE_MONTH_CELL = "ExcelDateMonthCell";

            dateDic = new Dictionary<int, DateTime>();
            var dayDic = new Dictionary<int, int>();

            DateTime? dateNenGetsu = null;

            var strExcelDateRowMode = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_ROW_MODE, string.Empty);
            if (string.IsNullOrEmpty(strExcelDateRowMode))
            {
                LoggerUxer.Error($"設定ファイルのExcel日付行指定モードが未設定です。[キー:{KEY_EXCEL_DATE_ROW_MODE}]");
                return false;
            }
            if (strExcelDateRowMode.ToLower() != EXCEL_DATE_ROW_MODE_FIXED.ToLower() &&
                strExcelDateRowMode.ToLower() != EXCEL_DATE_ROW_MODE_JUDGE.ToLower() &&
                strExcelDateRowMode.ToLower() != EXCEL_DATE_ROW_MODE_NONE.ToLower())
            {
                LoggerUxer.Error($"設定ファイルのExcel日付行指定モードが不正値です。[{KEY_EXCEL_DATE_ROW_MODE}:{strExcelDateRowMode}]");
                return false;
            }

            var excelDateRow = int.MinValue;

            if (strExcelDateRowMode.ToLower() == EXCEL_DATE_ROW_MODE_FIXED.ToLower())
            {
                // 日付行指定モードが固定値の場合
                var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_ROW, string.Empty);
                if (string.IsNullOrEmpty(str))
                {
                    LoggerUxer.Error($"設定ファイルのExcel日付行番号が未設定です。[キー:{KEY_EXCEL_DATE_ROW}]");
                    return false;
                }
                if (int.TryParse(str, out excelDateRow) == false || excelDateRow <= 0)
                {
                    LoggerUxer.Error($"設定ファイルのExcel日付行番号が不正値です。[{KEY_EXCEL_DATE_ROW}:{str}]");
                    return false;
                }
                LoggerUxer.Info($"[日付行番号（設定ファイル値）:{excelDateRow}]");
            }
            else if (strExcelDateRowMode.ToLower() == EXCEL_DATE_ROW_MODE_JUDGE.ToLower())
            {
                // 日付行指定モードが判定の場合

                // 日付行判定列
                var strJudgeCol = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_ROW_JUDGE_COL, string.Empty);
                if (string.IsNullOrEmpty(strJudgeCol))
                {
                    LoggerUxer.Error($"設定ファイルのExcel日付行判定列が未設定です。[キー:{KEY_EXCEL_DATE_ROW_JUDGE_COL}]");
                    return false;
                }
                if (!XlUxer.TryConvertColLetterToNumber(strJudgeCol, out var colNo))
                {
                    LoggerUxer.Error($"設定ファイルのExcel日付行判定列が不正値です。[{KEY_EXCEL_DATE_ROW_JUDGE_COL}:{strJudgeCol}]");
                    return false;
                }
                LoggerUxer.Info($"[日付行判定列:{strJudgeCol}(番号:{colNo})]");

                // 日付行判定値
                var strJudgeValue = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_ROW_JUDGE_VALUE, string.Empty);

                // 日付行の検索
                for (int row = 1; row <= 1000; row++)
                {
                    var cellValue = sheet.Cell(row, colNo).GetFormattedString();
                    if (cellValue == strJudgeValue)
                    {
                        // 日付行検出
                        excelDateRow = row;
                        break;
                    }
                }
                if (excelDateRow == int.MinValue)
                {
                    LoggerUxer.Error($"設定ファイルのExcel日付行判定が失敗しました。[{KEY_EXCEL_DATE_ROW_JUDGE_COL}:{strJudgeCol},{KEY_EXCEL_DATE_ROW_JUDGE_VALUE}:{strJudgeValue}]");
                    return false;
                }
                LoggerUxer.Info($"[日付行番号（判定結果）:{excelDateRow}]");
            }
            else if (strExcelDateRowMode.ToLower() == EXCEL_DATE_ROW_MODE_NONE.ToLower())
            {
                // 日付行指定モードがなしの場合
                LoggerUxer.Info($"日付行なし[{KEY_EXCEL_DATE_ROW_MODE}:{strExcelDateRowMode}]");
                return true;
            }
            else
            {
                LoggerUxer.Error($"設定ファイルのExcel日付行指定モードの扱いに失敗しました。[{KEY_EXCEL_DATE_ROW_MODE}:{strExcelDateRowMode}]");
                return false;
            }

            if (sheet.Row(excelDateRow).IsHidden)
            {
                LoggerUxer.Error($"設定ファイルのExcel日付行番号が非表示行です。[日付行番号:{excelDateRow}]");
                return false;
            }

            // 年と月の取得モード
            var strYMGetMode = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_YM_GET_MODE, string.Empty);
            if (string.IsNullOrEmpty(strYMGetMode))
            {
                LoggerUxer.Error($"設定ファイルのExcel年/月取得モードが未設定です。[キー:{KEY_EXCEL_DATE_YM_GET_MODE}]");
                return false;
            }
            // enum値変換
            ENUM_EXCEL_DATE_YM_GET_MODE ymGetMode = GetExcelDateYMGetModeFromIniValue(strYMGetMode);
            if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.None)
            {
                LoggerUxer.Error($"設定ファイルのExcel年/月取得モードが不正値です。[{KEY_EXCEL_DATE_YM_GET_MODE}:{strYMGetMode}]");
                return false;
            }

            if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromTargetCellDate)
            {
                // 特定セル値から取得する場合、ここで取得しておく
                var strAddress = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_NEN_GETSU_CELL_ADDRESS, string.Empty);
                if (string.IsNullOrEmpty(strAddress))
                {
                    LoggerUxer.Error($"設定ファイルのExcel年月セルアドレスが未設定です。[キー名:{KEY_EXCEL_NEN_GETSU_CELL_ADDRESS}]");
                    return false;
                }
                if (!ExcelCellValidatorUxer.IsValidA1Format(strAddress))
                {
                    LoggerUxer.Error($"設定ファイルのExcel年月セルアドレスが不正値です。[{KEY_EXCEL_NEN_GETSU_CELL_ADDRESS}:{strAddress}]");
                    return false;
                }
                var cellText = sheet.Cell(strAddress).GetFormattedString().Trim();
                if (!TryGetYmFromText(cellText, out dateNenGetsu))
                {
                    LoggerUxer.Error($"設定ファイルのExcel年月セルアドレス値に年月が含まれません。[{KEY_EXCEL_NEN_GETSU_CELL_ADDRESS}:{strAddress}]");
                    return false;
                }
            }

            int intYYYY = int.MinValue;
            int intMM = int.MinValue;
            if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromYMCells)
            {
                // 年月を年月セルから取得する場合

                // 年セル
                var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_YEAR_CELL, string.Empty);
                if (string.IsNullOrEmpty(str))
                {
                    LoggerUxer.Error($"設定ファイルのExcel年セルが未設定です。[キー:{KEY_EXCEL_DATE_YEAR_CELL}]");
                    return false;
                }
                var str2 = str.Trim().ToUpper();
                if (!ExcelCellValidatorUxer.IsValidA1Format(str2))
                {
                    LoggerUxer.Error($"設定ファイルのExcel年セルがA1形式として不正値です。[{KEY_EXCEL_DATE_YEAR_CELL}:{str}]");
                    return false;
                }

                // 見たままのテキスト値を取得
                // 空チェック
                str = sheet.Cell(str2).GetFormattedString().Trim();
                if (string.IsNullOrEmpty(str))
                {
                    LoggerUxer.Error($"年セルが未設定です。[セルアドレス:{str2}]");
                    return false;
                }
                // 数値と範囲チェック
                if (int.TryParse(str, out var intYear) == false)
                {
                    LoggerUxer.Error($"年セルが非数値です。[セルアドレス:{str2},セル値:{str}]");
                    return false;
                }
                if (intYear < 0)
                {
                    // エラー
                }
                else if (intYear <= 99)
                {
                    // 年2桁を4桁化
                    intYYYY = 2000 + intYear;
                }
                else if(intYear < 2000)
                {
                    // エラー
                }
                else if(intYear < 3000)
                {
                    // 年4桁はそのまま
                    intYYYY = intYear;
                }
                else
                {
                    // エラー
                }
                if (intYYYY == int.MinValue)
                {
                    LoggerUxer.Error($"年セルが範囲外です。[セルアドレス:{str2},セル値:{intYear}]");
                    return false;
                }
                LoggerUxer.Info($"[シートの年:{intYYYY}]");

                // 月セル
                str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_MONTH_CELL, string.Empty);
                if (string.IsNullOrEmpty(str))
                {
                    LoggerUxer.Error($"設定ファイルのExcel月セルが未設定です。[キー:{KEY_EXCEL_DATE_MONTH_CELL}]");
                    return false;
                }
                str2 = str.Trim().ToUpper();
                if (!ExcelCellValidatorUxer.IsValidA1Format(str2))
                {
                    LoggerUxer.Error($"設定ファイルのExcel月セルがA1形式として不正値です。[{KEY_EXCEL_DATE_MONTH_CELL}:{str}]");
                    return false;
                }

                // 見たままのテキスト値を取得
                // 空チェック
                str = sheet.Cell(str2).GetFormattedString().Trim();
                if (string.IsNullOrEmpty(str))
                {
                    LoggerUxer.Error($"月セルが未設定です。[セルアドレス:{str2}]");
                    return false;
                }
                // 数値と範囲チェック
                if (int.TryParse(str, out var intMonth) == false)
                {
                    LoggerUxer.Error($"月セルが非数値です。[セルアドレス:{str2},セル値:{str}]");
                    return false;
                }
                if (intMonth < 1)
                {
                    // エラー
                }
                else if (intMonth <= 12)
                {
                    // OK
                    intMM = intMonth;
                }
                else
                {
                    // エラー
                }
                if (intMM == int.MinValue)
                {
                    LoggerUxer.Error($"月セルが範囲外です。[セルアドレス:{str2},セル値:{intMonth}]");
                    return false;
                }
                LoggerUxer.Info($"[シートの月:{intMM}]");
            }

            if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromSheetName)
            {
                // シート名から取得
                var sheetName = sheet.Name.Trim();
                if (!GetYMFrom2Numerics(sheetName, out intYYYY, out intMM, out var messageForMe))
                {
                    LoggerUxer.Error($"シート名から年月を取得できませんでした。[シート名:{sheetName}, 詳細:{messageForMe}]");
                    return false;
                }
            }

            var strExcelDateStartCol = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_ROW_START_COL, string.Empty);

            //if (int.TryParse(strExcelDateStartCol, out var excelDateStartCol) == false || excelDateStartCol < 0)
            if (!XlUxer.TryConvertColLetterToNumber(strExcelDateStartCol, out int excelDateStartCol))
            {
                LoggerUxer.Error($"設定ファイルのExcel日付開始列が不正です。[{KEY_EXCEL_DATE_ROW_START_COL}:{strExcelDateStartCol}]");
                return false;
            }
            LoggerUxer.Info($"[日付開始列:{strExcelDateStartCol}（番号:{excelDateStartCol}）]");

            var strExcelDateRowEndValue = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATE_ROW_END_VALUE, string.Empty);

            // 日付開始セルから繰り返す
            for (int col = excelDateStartCol; ; col++)
            {
                if (col > 1000)
                {
                    // 終了値が見つからない
                    LoggerUxer.Error($"日付行で終了値が見つかりません。[日付行:{excelDateRow},日付行開始列:{strExcelDateStartCol},日付終了値:{strExcelDateRowEndValue}]");
                    return false;
                }

                // 非表示列は飛ばす
                if (sheet.Column(col).IsHidden) continue;

                // 日付列終了
                if (sheet.Cell(excelDateRow, col).GetFormattedString() == strExcelDateRowEndValue) break;

                DateTime date = DateTime.MinValue;

                if (dateNenGetsu != null)
                {
                    var dt = (DateTime)dateNenGetsu;
                    var text = sheet.Cell(excelDateRow, col).GetFormattedString().Trim();
                    if (!int.TryParse(text,out var intVal))
                    {
                        // 数値でないとき飛ばす
                        continue;
                    }
                    date = new DateTime(dt.Year, dt.Month, intVal);
                    // ディクショナリに追加
                    dateDic.Add(col, date);
                }
                else if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromDateCell)
                {
                    // 年月を日付値から取得する場合

                    if (sheet.Cell(excelDateRow, col).TryGetValue<DateTime>(out var d) &&
                        d.Year >= 2000)
                    {
                        // 日付型で取得できたらそのままリストに追加
                        date = d;
                    }
                    else if (int.TryParse(sheet.Cell(excelDateRow, col).GetFormattedString(), out var intValue) &&
                             intValue >= 1 && intValue <= 31)
                    {
                        // 日のみ数値で格納されている

                        if (dateDic.Count == 0)
                        {
                            // 最初のセルが日付でない場合飛ばす
                            LoggerUxer.Warn($"日付行の開始セルが日付ではありません。[日付行:{excelDateRow},列:{col},値:{sheet.Cell(excelDateRow, col).GetFormattedString()}]");
                            continue;
                        }
                        // 直前の日付の年と月を使用して日付を作る
                        date = new DateTime(
                            dateDic[dateDic.Keys.Max()].Year,
                            dateDic[dateDic.Keys.Max()].Month,
                            intValue);
                    }
                    else if (sheet.Cell(excelDateRow, col).TryGetValue<DateTime>(out d))
                    {
                        // 日付型で年が2000未満のとき、
                        if (dateDic.Count > 0 && d.Month == dateDic[dateDic.Max(m => m.Key)].Month)
                        {
                            d = new DateTime(
                                dateDic[dateDic.Max(m => m.Key)].Year,
                                d.Month,
                                d.Day);
                        }
                        else if (dateDic.Count > 0)
                        {
                            d = new DateTime(
                                dateDic[dateDic.Max(m => m.Key)].Month == 12 ?
                                    dateDic[dateDic.Max(m => m.Key)].Year + 1 :
                                    dateDic[dateDic.Max(m => m.Key)].Year,
                                d.Month,
                                d.Day);
                        }
                        else
                        {
                            LoggerUxer.Warn($"日付セルの年が不明です。[日付行:{excelDateRow},列:{col}]");
                            continue;
                        }
                        date = d;
                    }
                    else
                    {
                        // 日付でも数値でも無い場合、飛ばす
                        LoggerUxer.Warn($"日付セルが日付または数値ではありません。[日付行:{excelDateRow},列:{col}]");
                        continue;
                    }
                    if (dateDic.Count > 0 && date <= dateDic.Values.Max(m => m))
                    {
                        // 前の日付より昔の日付の場合、飛ばす
                        LoggerUxer.Warn($"日付行のセルの日付が重複または逆転しています。[日付行:{excelDateRow},列:{col},値:{sheet.Cell(excelDateRow, col).GetFormattedString()}]");
                        continue;
                    }
                    if (toDate != null && (date < fromDate || date > (DateTime)toDate))
                    {
                        // 終了日指定ありで、開始から終了の範囲外のとき、飛ばす
                        continue;
                    }

                    // ディクショナリに追加
                    dateDic.Add(col, date);
                }
                else if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromYMCells)
                {
                    // 年月を年月セルから取得する場合

                    // 表示されているままの文字列（通常日のみ）を取得
                    var str = sheet.Cell(excelDateRow, col).GetFormattedString().Trim();

                    if (string.IsNullOrEmpty(str) || int.TryParse(str, out var day) == false || day < 1)
                    {
                        // 空または数値でない、またはマイナス値の場合、飛ばす
                        LoggerUxer.Warn($"日付セルが数値ではありません。[日付行:{excelDateRow},列:{ExcelColumnHelperUxer.GetExcelColumnLetter(col)}（番号:{col}）,値:{str}]");
                        continue;
                    }

                    // 日のみDicに追加
                    dayDic.Add(col, day);
                }
                else if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromSheetName)
                {
                    // 年月をシート名から取得する場合

                    // 表示されているままの文字列（通常日のみ）を取得
                    var str = sheet.Cell(excelDateRow, col).GetFormattedString().Trim();

                    if (string.IsNullOrEmpty(str) || int.TryParse(str, out var day) == false || day < 1)
                    {
                        // 空または数値でない、またはマイナス値の場合、飛ばす
                        LoggerUxer.Warn($"日付セルが数値ではありません。[日付行:{excelDateRow},列:{ExcelColumnHelperUxer.GetExcelColumnLetter(col)}（番号:{col}）,値:{str}]");
                        continue;
                    }

                    // 日のみDicに追加
                    dayDic.Add(col, day);
                }
                else
                {
                    LoggerUxer.Error($"設定ファイルのExcel年/月取得モードが不正値です。[{KEY_EXCEL_DATE_YM_GET_MODE}:{strYMGetMode}]");
                    return false;
                }
            }
            if ((ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromDateCell && dateDic.Count == 0) ||
                (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromYMCells && dayDic.Count == 0))
            {
                // 日付なし
                LoggerUxer.Error($"Excelの日付行情報が取得できませんでした。[日付行:{excelDateRow},日付開始列:{strExcelDateStartCol},日付終了値:{strExcelDateRowEndValue}]");
                return false;
            }

            if (ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromYMCells ||
                ymGetMode == ENUM_EXCEL_DATE_YM_GET_MODE.FromSheetName)
            {
                int maeDay = int.MinValue;
                bool isTochuDown = false;
                foreach (var col in dayDic.Keys)
                {
                    if (dayDic[col] < maeDay)
                    {
                        isTochuDown = true;
                        break;
                    }
                    maeDay = dayDic[col];
                }
                if (isTochuDown)
                {
                    // 二月跨っている
                    var maeYear = intMM == 1 ? intYYYY - 1 : intYYYY;
                    var maeMonth = intMM == 1 ? 12 : intMM - 1;
                    var isMae = true;
                    dateDic.Clear();
                    maeDay = int.MinValue;
                    foreach (var col in dayDic.Keys)
                    {
                        if (isMae && (dayDic[col] < maeDay))
                        {
                            isMae = false;
                        }
                        if (isMae)
                            dateDic.Add(col, new DateTime(maeYear, maeMonth, dayDic[col]));
                        else
                            dateDic.Add(col, new DateTime(intYYYY, intMM, dayDic[col]));
                        maeDay = dayDic[col];
                    }
                }
                else
                {
                    // 1月のみ
                    dateDic.Clear();
                    foreach (var col in dayDic.Keys)
                    {
                        dateDic.Add(col, new DateTime(intYYYY, intMM, dayDic[col]));
                    }
                }
            }

            // 年月補正
            UpdateYearMonth(dateDic);

            // シート名から年補正
            if (StringUxer.TryExtractYearFromString(sheet.Name, out var yearFromSheetName))
            {
                var oldYear = dateDic[dateDic.Max(m => m.Key)].Year;
                var sa = yearFromSheetName - oldYear;
                if (sa != 0)
                {
                    var keys = dateDic.Keys.ToList();
                    foreach (var key in keys)
                    {
                        dateDic[key] = new DateTime(
                            dateDic[key].Year + sa,
                            dateDic[key].Month,
                            dateDic[key].Day);
                    }
                    LoggerUxer.Info($"シート名より日付の年を補正しました。[補正前:{oldYear},補正後:{yearFromSheetName}]");
                }
            }

            LoggerUxer.Info($"[日付:{GetDatesString(dateDic)}]");
            return true;
        }

        /// <summary>
        /// 文字列から年月を取得
        /// </summary>
        /// <param name="inputText"></param>
        /// <param name="year"></param>
        /// <param name="month"></param>
        /// <param name="resultMessage"></param>
        /// <returns></returns>
        private static bool GetYMFrom2Numerics(
            string inputText,
            out int year,
            out int month,
            out string resultMessage)
        {
            year = -1;
            month = -1;
            resultMessage = string.Empty;

            // 1. 数字の塊を抽出
            MatchCollection matches = Regex.Matches(inputText, @"\d+");
            if (matches.Count < 2)
            {
                resultMessage = "数字が2つ以上含まれていません。";
                return false;
            }

            // 先頭の2つを取得
            string rawYearOrTerm = matches[0].Value;
            string rawMonth = matches[1].Value;

            // 数値に変換
            int yearOrTerm = int.Parse(rawYearOrTerm);
            month = int.Parse(rawMonth);

            // 2. 2個目の数字（月）のチェック
            if (month < 1 || month > 12)
            {
                resultMessage = $"月の値（{month}）が1～12の範囲外です。";
                return false;
            }

            bool isTerm = false;

            // 3. 1個目の数字（年・期）の変換
            if (rawYearOrTerm.Length == 4)
            {
                year = yearOrTerm; // 4桁はそのまま西暦
            }
            else if (rawYearOrTerm.Length == 2)
            {
                year = yearOrTerm + 2000; // 2桁は2000を足す
            }
            else if (rawYearOrTerm.Length == 3) // ★将来「99期」などがあるなら「期フラグ」を紐付けるのが安全です
            {
                year = yearOrTerm + 1923; // 3桁は期（1923を足す）
                isTerm = true;
            }
            else
            {
                resultMessage = $"1つ目の数字（{rawYearOrTerm}）の桁数が不正です。";
                return false;
            }

            // 4. 期のときの年度補正（1～3月のときは西暦に1足す）
            if (isTerm && (month >= 1 && month <= 3))
            {
                year += 1;
            }

            // 念のため
            if (year > DateTime.Today.Year + 1)
            {
                resultMessage = $"年（{year}）が不正です。";
                return false;
            }
            return true;
        }

        /// <summary>
        /// INI値から日付並び年月取得モードenum値を取得
        /// </summary>
        /// <param name="iniValue">INI値（文字列）</param>
        /// <returns>enum値（該当なしはNone）</returns>
        private static ENUM_EXCEL_DATE_YM_GET_MODE GetExcelDateYMGetModeFromIniValue(string iniValue)
        {
            ENUM_EXCEL_DATE_YM_GET_MODE ret = ENUM_EXCEL_DATE_YM_GET_MODE.None;
            if (iniValue.ToLower() == KEY_EXCEL_DATE_YM_GET_MODE_FROM_YM_CELLS.ToLower())
            {
                ret = ENUM_EXCEL_DATE_YM_GET_MODE.FromYMCells;
            }
            else if (iniValue.ToLower() == KEY_EXCEL_DATE_YM_GET_MODE_FROM_DATE_CELL.ToLower())
            {
                ret = ENUM_EXCEL_DATE_YM_GET_MODE.FromDateCell;
            }
            else if (iniValue.ToLower() == KEY_EXCEL_DATE_YM_GET_MODE_FROM_TARGET_CELL_DATE.ToLower())
            {
                ret = ENUM_EXCEL_DATE_YM_GET_MODE.FromTargetCellDate;
            }
            else if (iniValue.ToLower() == KEY_EXCEL_DATE_YM_GET_MODE_FROM_SHEET_NAME.ToLower())
            {
                ret = ENUM_EXCEL_DATE_YM_GET_MODE.FromSheetName;
            }
            return ret;
        }

        private static void UpdateYearMonth(Dictionary<int,DateTime> dicDate)
        {
            // 更新前の日付文字列
            string before = GetDatesString(dicDate);

            // 年月（yyyyMM）でグループ化
            var ymGroups = dicDate.Values
                .Select(dt => dt.Date)
                .GroupBy(dt => dt.Year * 100 + dt.Month)
                .OrderBy(g => g.Key)
                .ToList();

            if (ymGroups.Count == 2)
            {
                var firstYm = ymGroups[0].Key;
                var secondYm = ymGroups[1].Key;

                // secondYmの1月前を計算
                var expectedYm = (secondYm % 100 == 1)
                    ? (secondYm / 100 - 1) * 100 + 12
                    : secondYm - 1;

                if (firstYm != expectedYm)
                {
                    // 更新対象の年月
                    var newDate = new DateTime(expectedYm / 100, expectedYm % 100, 1);

                    foreach (var key in dicDate.Keys.ToList())
                    {
                        var dt = dicDate[key];
                        if (dt.Year * 100 + dt.Month == firstYm)
                        {
                            // 日付はそのまま、年月だけ更新
                            dicDate[key] = new DateTime(newDate.Year, newDate.Month, dt.Day);
                        }
                    }
                    LoggerUxer.Info($"前の年月を {expectedYm} に補正[補正前:{before}][補正後:{GetDatesString(dicDate)}]");
                }
            }
        }

        private static string GetDatesString(Dictionary<int,DateTime> dic)
        {
            string str = string.Empty;
            int maeYyyy = -1;
            int maeMm = -1;
            foreach (var day in dic.Values.OrderBy(m => m))
            {
                if (string.IsNullOrEmpty(str))
                {
                    str = day.ToString("yyyy/M/d");
                    maeYyyy = day.Year;
                    maeMm = day.Month;
                    continue;
                }
                str += ",";
                if (day.Year==maeYyyy&&day.Month==maeMm)
                {
                    str += day.Day.ToString();
                }
                else if(day.Year == maeYyyy)
                {
                    str += day.Month.ToString()+"/" +day.Day.ToString();
                }
                else
                {
                    str += day.ToString("yyyy/M/d");
                }
                maeYyyy = day.Year;
                maeMm = day.Month;
            }
            return str;
        }

        // ライン（複数）情報取得
        private static bool TryCreateLineInfoList(
            IXLWorksheet sheet,
            Dictionary<string,string> iniDic,
            LineFilterUxer lineFilter,
            out List<ExcelLineDto> lineInfoList,
            bool isTopOnly = false)
        {
            const string KEY_NAME_TO_ID_LIST_FOR_LINE = "NameToIdListForLine";
            const string KEY_EXCEL_LINE_JUDGE_COL = "ExcelLineJudgeCol";
            //const string LINE_LABEL = "ライン";
            const string KEY_EXCEL_LINE_JUDGE_VALUE = "ExcelLineJudgeValue";
            const string KEY_EXCEL_DATA_START_ROW_JUDGE_COL = "ExcelDataStartRowJudgeCol";
            const string KEY_EXCEL_DATA_START_ROW_JUDGE_VALUES = "ExcelDataStartRowJudgeValues";
            const string KEY_EXCEL_LINES_ON_SHEET = "ExcelLinesOnSheet";

            // データ開始行判定値
            List<string> dataStartRowJudgeValues = new List<string>();

            lineInfoList = new List<ExcelLineDto>();

            // 1シートに複数ラインあり
            var linesInSheet = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_LINES_ON_SHEET, string.Empty) == "1";
            LoggerUxer.Info($"[シート内に複数ライン:{linesInSheet}]");
            isTopOnly = !linesInSheet;

            // ライン判定値
            var lineJudgeValue = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_LINE_JUDGE_VALUE, string.Empty);
            if (string.IsNullOrEmpty(lineJudgeValue))
            {
                LoggerUxer.Error($"設定ファイルにライン判定値が設定されていません。[キー:{KEY_EXCEL_LINE_JUDGE_VALUE}]");
                return false;
            }
            LoggerUxer.Info($"[ライン判定値:{lineJudgeValue}]");

            // ライン名→ID変換辞書
            if (!DictionaryUxer.CreateNameToIdDic(
                DictionaryUxer.GetValueOrDefault(iniDic, KEY_NAME_TO_ID_LIST_FOR_LINE, string.Empty),
                out var dicLine, out var myMessage))
            {
                LoggerUxer.Error($"ライン名→ID変換辞書の作成に失敗しました。[キー:{KEY_NAME_TO_ID_LIST_FOR_LINE},エラー内容:{myMessage}]");
                return false;
            }

            var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_LINE_JUDGE_COL, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"設定ファイルにライン判定列が設定されていません。[キー:{KEY_EXCEL_LINE_JUDGE_COL}]");
                return false;
            }
            //if (!int.TryParse(str, out var lineJudgeCol) || lineJudgeCol < 1)
            if (!XlUxer.TryConvertColLetterToNumber(str, out var lineJudgeCol))
            {
                LoggerUxer.Error($"設定ファイルのライン判定列が不正です。[キー:{KEY_EXCEL_LINE_JUDGE_COL},値:{str}]");
                return false;
            }
            LoggerUxer.Info($"[ライン判定列:{str}（番号:{lineJudgeCol}）]");

            str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_START_ROW_JUDGE_COL, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"設定ファイルにデータ開始行判定列が設定されていません。[キー:{KEY_EXCEL_DATA_START_ROW_JUDGE_COL}]");
                return false;
            }
            //if (!int.TryParse(str, out var dataStartRowJudgeCol) || dataStartRowJudgeCol < 1)
            if (!XlUxer.TryConvertColLetterToNumber(str, out var dataStartRowJudgeCol))
            {
                LoggerUxer.Error($"設定ファイルのデータ開始行判定列が不正です。[キー:{KEY_EXCEL_DATA_START_ROW_JUDGE_COL},値:{str}]");
                return false;
            }
            LoggerUxer.Info($"[データ開始行判定列:{str}(番号:{dataStartRowJudgeCol})]");

            // データ開始行判定値
            str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_START_ROW_JUDGE_VALUES, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"設定ファイルにデータ開始行判定値が設定されていません。[キー:{KEY_EXCEL_DATA_START_ROW_JUDGE_VALUES}]");
                return false;
            }
            dataStartRowJudgeValues = str.Split(',').ToList();
            LoggerUxer.Info($"[データ開始行判定値:{string.Join(",",dataStartRowJudgeValues.ToArray())}]");

            // 1000行まで走査
            for (var row = 1; row <= 1000; row++)
            {
                str = sheet.Cell(row, lineJudgeCol).GetFormattedString().Trim();
                if (!KanaConvertUxer.TryConvertHankakuToZenkaku(str, out var cellValue, out var messageForMe))
                {
                    LoggerUxer.Error($"ライン名の全角化に失敗しました。[エラー内容:{messageForMe}]");
                    return false;
                }
                if (cellValue == lineJudgeValue)
                {
                    // ライン名（同列の1行下）
                    var lineName = sheet.Cell(row + 1, lineJudgeCol).GetFormattedString().Trim();

                    // 対象ライン以外は飛ばす
                    if (!lineFilter.CanPass(lineName.Trim()))
                    {
                        LoggerUxer.Info($"対象ライン以外をスキップします。[ライン名:{lineName}]");
                        continue;
                    }

                    var lineId = DictionaryUxer.GetValueOrDefault(dicLine, lineName.Replace(",", ""), int.MinValue);
                    if (lineId == int.MinValue)
                    {
                        lineId = DictionaryUxer.GetValueOrDefault(dicLine, lineName, int.MinValue);
                    }
                    if (lineId == int.MinValue)
                    {
                        LoggerUxer.Error($"ライン名が{KEY_NAME_TO_ID_LIST_FOR_LINE}に登録されていません。[ライン名:{lineName}]");
                        return false;
                    }
                    LoggerUxer.Info($"[ライン名:{lineName},ラインID:{lineId}]");

                    // 先頭ラインのみの場合、既に1件でもリストにあれば、ループ脱出（念のため）
                    if (isTopOnly && lineInfoList.Count > 0) break;

                    lineInfoList.Add(new ExcelLineDto()
                    {
                        LineId = lineId,
                        LineName = lineName,
                        ShiftName = sheet.Cell(row + 1, lineJudgeCol + 1).GetFormattedString().Trim(),
                        DataStartRow = 0,
                        LineCol = lineJudgeCol,
                    });
                }

                // データ開始行判定
                var dataStartRowJudgeCell = sheet.Cell(row, dataStartRowJudgeCol).GetFormattedString().Trim();
                if (!string.IsNullOrEmpty(dataStartRowJudgeCell) && dataStartRowJudgeValues.Contains(dataStartRowJudgeCell))
                {
                    if (lineInfoList.Count > 0)
                    {
                        // データ開始行
                        var dataStartRow = row + 1;
                        if (string.IsNullOrEmpty(sheet.Cell(dataStartRow, dataStartRowJudgeCol).GetFormattedString().Trim()))
                            dataStartRow++;
                        lineInfoList.Last().DataStartRow = dataStartRow;
                        LoggerUxer.Info($"[データ開始行:{dataStartRow}]");

                        // 先頭ラインのみの場合、ループ脱出
                        if (isTopOnly) break;
                    }
                }
            }
            if (lineInfoList.Count == 0)
            {
                // ラインなし
                LoggerUxer.Warn($"ライン情報が見つかりませんでした。シートをスキップします。[シート名:{sheet.Name}]");
                return true;
            }

            var noStartRowLine = lineInfoList.FirstOrDefault(m => m.DataStartRow == 0);
            if (noStartRowLine != null)
            {
                // データ開始行が見つからなかったラインあり
                LoggerUxer.Error($"開始行が見つからないラインがありました。[シート名:{sheet.Name},ライン名:{noStartRowLine.LineName}]");
                return false;
            }
            return true;
        }

        // ファイルパスよりラインID取得
        private static bool TryGetLineIdFromFilePath(
            string filePath,
            Dictionary<string, string> iniDic,
            out int lineId)
        {
            const string KEY_EXCEL_FILE_NAME_PATTERN_TO_LINE = "ExcelFileNamePatternToLine";

            lineId = -1;

            // ファイル名からライン判定
            var fnptl = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_FILE_NAME_PATTERN_TO_LINE, string.Empty);
            if (!string.IsNullOrEmpty(fnptl))
            {
                LoggerUxer.Info($"ファイル名からラインIDを取得します。[ファイルパス:{filePath}]");

                var patterns = fnptl.Split('|');
                foreach (var pattern in patterns)
                {
                    var items = pattern.Split(',');
                    if (items.Length != 2)
                    {
                        LoggerUxer.Error(
                            $"ファイル名からライン判定のパターン定義が不正です。" +
                            $"[{KEY_EXCEL_FILE_NAME_PATTERN_TO_LINE}:{fnptl}]");
                        return false;
                    }
                    if (StringUxer.WildcardMatch(filePath, items[0]))
                    {
                        if (!int.TryParse(items[1].Trim(), out var intVal))
                        {
                            LoggerUxer.Error(
                                $"ファイル名からライン判定のパターン定義でラインIDが数値ではありません。" +
                                $"[{KEY_EXCEL_FILE_NAME_PATTERN_TO_LINE}:{fnptl}]");
                            return false;
                        }
                        lineId = intVal;
                    }
                }
                if (lineId == -1)
                {
                    LoggerUxer.Error(
                        $"ファイル名からライン判定で合致するパターンがありませんでした。" +
                        $"[ファイル名:{filePath},{KEY_EXCEL_FILE_NAME_PATTERN_TO_LINE}:{fnptl}]");
                    return false;
                }
                return true;
            }
            LoggerUxer.Info($"ファイル名からラインIDは取得しません。[ファイルパス:{filePath}]");
            return true;
        }

        // シート内のセルよりラインID取得
        private static bool TryGetLineIdFromCell(
            IXLWorksheet sheet,
            Dictionary<string, string> iniDic,
            out int lineId)
        {
            const string KEY_EXCEL_LINE_KEY_CELL = "ExcelLineKeyCell";
            const string KEY_EXCEL_LINE_KEY_VALUE_PAIR = "ExcelLineKeyValuePair";

            lineId = -1;

            var lineCell = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_LINE_KEY_CELL, string.Empty);
            if (!string.IsNullOrEmpty(lineCell))
            {
                LoggerUxer.Info($"セル値からラインIDを取得します。[ラインID取得セル:{lineCell}]");

                if (!XlUxer.TryValidateA1CellAddress(lineCell, out var _))
                {
                    LoggerUxer.Error($"ライン取得セルアドレスが不正です。[{KEY_EXCEL_LINE_KEY_CELL}:{lineCell}]");
                    return false;
                }

                var lineConvStr = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_LINE_KEY_VALUE_PAIR, string.Empty);
                if (string.IsNullOrEmpty(lineConvStr))
                {
                    LoggerUxer.Info($"[ラインID取得なし]");
                    return true;
                }
                LoggerUxer.Info($"[ラインID取得セル:{lineCell}]");

                // ライン判定値
                var lineJudgeValue = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_LINE_KEY_VALUE_PAIR, string.Empty);
                if (string.IsNullOrEmpty(lineJudgeValue))
                {
                    LoggerUxer.Error($"設定ファイルにライン判定値が設定されていません。[キー:{KEY_EXCEL_LINE_KEY_VALUE_PAIR}]");
                    return false;
                }
                LoggerUxer.Info($"[ライン判定値:{lineJudgeValue}]");

                // ライン名→ID変換辞書
                var convList = lineJudgeValue.Split('|').ToList();
                Dictionary<string, string> convDic = new Dictionary<string, string>();
                foreach (var str in convList)
                {
                    var items = str.Split(',');
                    if (items.Length < 2) continue;
                    convDic.Add(items[0], items[1]);
                }
                if (convDic.Count == 0)
                {
                    LoggerUxer.Error($"ラインID辞書が0件です。[キー:{KEY_EXCEL_LINE_KEY_VALUE_PAIR}]");
                    return false;
                }

                // ライン名（というか変換元文字列）のセル値取得
                var lineValue = sheet.Cell(lineCell).GetFormattedString();

                // 辞書変換
                if (!convDic.ContainsKey(lineValue))
                {
                    LoggerUxer.Error(
                        $"ラインID辞書に登録されていません。" +
                        $"[{KEY_EXCEL_LINE_KEY_VALUE_PAIR}:{lineJudgeValue}," +
                        $"{KEY_EXCEL_LINE_KEY_CELL}:{lineCell}," +
                        $"セル値：{lineValue}]");
                    return false;
                }

                // ラインID
                var strLineId = convDic[lineValue];
                if (!int.TryParse(strLineId, out var intValue))
                {
                    LoggerUxer.Error(
                        $"ラインIDが数値ではありません。" +
                        $"[{KEY_EXCEL_LINE_KEY_VALUE_PAIR}:{lineJudgeValue}," +
                        $"{KEY_EXCEL_LINE_KEY_CELL}:{lineCell}," +
                        $"セル値：{lineValue}," +
                        $"ラインID値:{strLineId}");
                    return false;
                }
                lineId = intValue;
                return true;
            }
            LoggerUxer.Info($"セルからのラインID取得なし。[キー：{KEY_EXCEL_LINE_KEY_CELL}]");
            return true;
        }

        // 開始行取得
        private static bool TryGetStartRow(
            IXLWorksheet sheet,
            Dictionary<string, string> iniDic,
            out int startRow)
        {
            const string KEY_EXCEL_DATA_START_ROW_JUDGE_COL = "ExcelDataStartRowJudgeCol";
            const string KEY_EXCEL_DATA_START_ROW_JUDGE_VALUES = "ExcelDataStartRowJudgeValues";

            startRow = -1;

            // データ開始行判定値
            List<string> dataStartRowJudgeValues = new List<string>();

            var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_START_ROW_JUDGE_COL, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"設定ファイルにデータ開始行判定列が設定されていません。[キー:{KEY_EXCEL_DATA_START_ROW_JUDGE_COL}]");
                return false;
            }
            if (!XlUxer.TryConvertColLetterToNumber(str, out var dataStartRowJudgeCol))
            {
                LoggerUxer.Error($"設定ファイルのデータ開始行判定列が不正です。[キー:{KEY_EXCEL_DATA_START_ROW_JUDGE_COL},値:{str}]");
                return false;
            }
            LoggerUxer.Info($"[データ開始行判定列:{str}(番号:{dataStartRowJudgeCol})]");

            // データ開始行判定値
            str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_START_ROW_JUDGE_VALUES, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"設定ファイルにデータ開始行判定値が設定されていません。[キー:{KEY_EXCEL_DATA_START_ROW_JUDGE_VALUES}]");
                return false;
            }
            dataStartRowJudgeValues = str.Split(',').ToList();
            LoggerUxer.Info($"[データ開始行判定値:{string.Join(",", dataStartRowJudgeValues.ToArray())}]");

            // 1000行まで走査
            for (var row = 1; row <= 1000; row++)
            {
                // データ開始行判定
                var dataStartRowJudgeCell = sheet.Cell(row, dataStartRowJudgeCol).GetFormattedString().Trim();
                if (!string.IsNullOrEmpty(dataStartRowJudgeCell) &&
                    dataStartRowJudgeValues.Contains(dataStartRowJudgeCell))
                {
                    // データ開始行
                    var dataStartRow = row + 1;

                    // 👇多分不要なので削除
                    //if (string.IsNullOrEmpty(sheet.Cell(dataStartRow, dataStartRowJudgeCol).GetFormattedString().Trim()))
                    //{
                    //    dataStartRow++;
                    //}

                    startRow = dataStartRow;
                    LoggerUxer.Info($"[データ開始行:{startRow}]");
                    break;
                }
            }
            return !(startRow == -1);
        }

        /// <summary>
        /// Excelシートよりデータ取得
        /// </summary>
        /// <param name="sheet">シート</param>
        /// <param name="lineInfo">ライン情報</param>
        /// <param name="dateDic">日付辞書</param>
        /// <param name="fromDate">開始日</param>
        /// <param name="iniDic">INI辞書</param>
        /// <param name="weOrderList">[参照]WE生産計画詳細リスト</param>
        /// <param name="tactManagementList">[参照]タクト管理リスト</param>
        /// <returns>成否</returns>
        private static bool GetData(
            IXLWorksheet sheet,
            ExcelLineDto lineInfo,
            DateTime now,
            Dictionary<int,DateTime> dateDic,
            DateTime fromDate,
            Dictionary<string, string> iniDic,
            List<WeOrderDto> weOrderList)
        {
            const string KEY_EXCEL_DATA_END_JUDGE_COL = "ExcelDataEndJudgeCol";
            const string KEY_EXCEL_DATA_END_JUDGE_VALUE = "ExcelDataEndJudgeValue";
            const string KEY_EXCEL_HIDDEN_ROW_HANDLING = "ExcelHiddenRowHandling";
            const string KEY_EXCEL_AF_WE_COL = "ExcelAfWeCol";

            const string HIDDEN_ROW_HANDLING_SKIP = "skip";
            const string HIDDEN_ROW_HANDLING_INCLUDE = "include";
            const string HIDDEN_ROW_HANDLING_WARN = "warn";

            // データ終了判定列
            var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_END_JUDGE_COL, string.Empty);
            //if (int.TryParse(str, out var excelDataEndJudgeCol) == false || excelDataEndJudgeCol < 1)
            if (!XlUxer.TryConvertColLetterToNumber(str, out var excelDataEndJudgeCol))
            {
                LoggerUxer.Error($"設定ファイルのExcelデータ終了判定列が未設定または不正な値です。[{KEY_EXCEL_DATA_END_JUDGE_COL}:{str}]");
                return false;
            }
            LoggerUxer.Info($"[データ終了判定列:{str}（番号:{excelDataEndJudgeCol}）]");

            // データ終了判定値
            var excelDataEndJudgeValue = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_END_JUDGE_VALUE, string.Empty);

            // 非表示行の扱い（未指定時はwarn）
            var hiddenRowHandlinng = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_HIDDEN_ROW_HANDLING, HIDDEN_ROW_HANDLING_WARN);
            LoggerUxer.Info($"[非表示行の扱い({KEY_EXCEL_HIDDEN_ROW_HANDLING})={hiddenRowHandlinng}]");

            // MC生産負荷行番号の取得
            if (!GetMcSeisanFukaRow(sheet, iniDic, out var mcSeisanFukaRow))
            {
                return false;
            }
            if (mcSeisanFukaRow >= 1)
            {
                LoggerUxer.Info($"[MC生産負荷行番号:{mcSeisanFukaRow}]");
            }

            // AF/WE列
            str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_AF_WE_COL, string.Empty);
            if (str == string.Empty)
            {
                LoggerUxer.Error($"設定ファイルにAF/WE列が登録されていません。[キー:{KEY_EXCEL_AF_WE_COL}]");
                return false;
            }
            if (!XlUxer.TryConvertColLetterToNumber(str, out var excelAfWeCol))
            {
                LoggerUxer.Error($"設定ファイルのAF/WE列の設定値が不正です。[{KEY_EXCEL_AF_WE_COL}:{str}]");
                return false;
            }
            LoggerUxer.Info($"[AF/WE列:{str}（番号:{excelAfWeCol}）]");

            // データ開始行から繰り返す
            for (int row = lineInfo.DataStartRow; ; row++)
            {
                // データ行終了値が見つからない
                if (row > 1000)
                {
                    LoggerUxer.Error($"データ終了判定値が見つかりません。[{KEY_EXCEL_DATA_END_JUDGE_COL}:{excelDataEndJudgeCol},{KEY_EXCEL_DATA_END_JUDGE_VALUE}:{excelDataEndJudgeValue}]");
                    return false;
                }

                // データ行終了判定
                var judgeValue = sheet.Cell(row, excelDataEndJudgeCol).GetFormattedString();
                if (StringUxer.WildcardMatch(
                    judgeValue,
                    excelDataEndJudgeValue))
                {
                    LoggerUxer.Info($"[データ行終了判定値検出行：{row},判定値:{judgeValue}]");
                    break;
                }

                if (sheet.Row(row).IsHidden)
                {
                    // 非表示行の時
                    switch(hiddenRowHandlinng.ToLower())
                    {
                        case HIDDEN_ROW_HANDLING_SKIP:
                            // スキップ
                            continue;
                        case HIDDEN_ROW_HANDLING_INCLUDE:
                            // 対象
                            break;
                        case HIDDEN_ROW_HANDLING_WARN:
                            // 警告（ログ＋スキップ）
                            LoggerUxer.Warn($"非表示行をスキップします。[行番号:{row}]");
                            continue;
                        default:
                            // 無効値
                            LoggerUxer.Error($"設定ファイルの非表示行の扱いに無効な値が設計されています。[キー:{KEY_EXCEL_HIDDEN_ROW_HANDLING},値:{hiddenRowHandlinng}]");
                            return false;
                    }
                }

                //// 機種コード（基本部番）が空の行は飛ばす（GCellは結合セルも加味している）
                //if (string.IsNullOrEmpty(XlUxer.GCell(sheet, row, lineInfo.LineCol).GetFormattedString())) continue;
                // AF/WE判定列が空の行は飛ばす
                if (string.IsNullOrEmpty(XlUxer.GCell(sheet, row, excelAfWeCol).GetFormattedString())) continue;

                // WE生産計画詳細データ取得
                if (!GetWeOrderFromSheet(sheet, iniDic, lineInfo, now, row, dateDic, fromDate, excelAfWeCol, mcSeisanFukaRow, weOrderList))
                {
                    return false;
                }
            }
            return true;
        }

        private static bool GetDataEazy(
            IXLWorksheet sheet,
            DateTime now,
            Dictionary<int, DateTime> dateDic,
            DateTime fromDate,
            Dictionary<string, string> iniDic,
            List<ColumnDefinition> cdList,
            ExcelSettingsDto excelSettings,
            List<string[]> outList)
        {
            const string KEY_EXCEL_DATA_END_JUDGE_COL = "ExcelDataEndJudgeCol";
            const string KEY_EXCEL_DATA_END_JUDGE_VALUE = "ExcelDataEndJudgeValue";
            const string KEY_EXCEL_HIDDEN_ROW_HANDLING = "ExcelHiddenRowHandling";
            const string KEY_EXCEL_AF_WE_COL = "ExcelAfWeCol";
            const string KEY_EXCEL_ROW_KIND_COL = "ExcelRowKindCol";
            const string KEY_EXCEL_ROW_KIND_TARGETS = "ExcelRowKindTargets";

            const string HIDDEN_ROW_HANDLING_SKIP = "skip";
            const string HIDDEN_ROW_HANDLING_INCLUDE = "include";
            const string HIDDEN_ROW_HANDLING_WARN = "warn";

            // データ終了判定列
            var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_END_JUDGE_COL, string.Empty);
            if (!XlUxer.TryConvertColLetterToNumber(str, out var excelDataEndJudgeCol))
            {
                LoggerUxer.Error($"設定ファイルのExcelデータ終了判定列が未設定または不正な値です。[{KEY_EXCEL_DATA_END_JUDGE_COL}:{str}]");
                return false;
            }
            LoggerUxer.Info($"[データ終了判定列:{str}（番号:{excelDataEndJudgeCol}）]");

            // データ終了判定値
            var excelDataEndJudgeValue = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_DATA_END_JUDGE_VALUE, string.Empty);
            var dataEndRetryCount = 0;
            if (excelDataEndJudgeValue.IndexOf(",") >= 0)
            {
                // カンマあり
                var items = excelDataEndJudgeValue.Split(',');
                if (items.Length == 2)
                {
                    if (int.TryParse(items[1], out var intValue))
                    {
                        dataEndRetryCount = intValue;
                        excelDataEndJudgeValue = items[0];
                    }
                }
            }

            // 非表示行の扱い（未指定時はwarn）
            var hiddenRowHandlinng = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_HIDDEN_ROW_HANDLING, HIDDEN_ROW_HANDLING_WARN);
            LoggerUxer.Info($"[非表示行の扱い({KEY_EXCEL_HIDDEN_ROW_HANDLING})={hiddenRowHandlinng}]");

            // AF/WE列
            str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_AF_WE_COL, string.Empty);
            var strK = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_ROW_KIND_COL, string.Empty);
            if (string.IsNullOrEmpty(str) && string.IsNullOrEmpty(strK))
            {
                LoggerUxer.Error($"設定ファイルにAF/WE列または行種類列が登録されていません。[キー:{KEY_EXCEL_AF_WE_COL},{KEY_EXCEL_ROW_KIND_COL}]");
                return false;
            }
            var excelAfWeCol = 0;
            if (!string.IsNullOrEmpty(str) && !XlUxer.TryConvertColLetterToNumber(str, out excelAfWeCol))
            {
                LoggerUxer.Error($"設定ファイルのAF/WE列の設定値が不正です。[{KEY_EXCEL_AF_WE_COL}:{str}]");
                return false;
            }
            var excelRowKindColNoList = new List<int>();
            if (!string.IsNullOrEmpty(strK))
            {
                var colNos = strK.Split('+');
                foreach (var colNo in colNos)
                {
                    var intVal = -1;
                    if (!string.IsNullOrEmpty(colNo) && !XlUxer.TryConvertColLetterToNumber(colNo, out intVal))
                    {
                        LoggerUxer.Error($"設定ファイルの行種類列の設定値が不正です。[{KEY_EXCEL_ROW_KIND_COL}:{strK}]");
                        return false;
                    }
                    if (intVal != -1) excelRowKindColNoList.Add(intVal);
                }
            }
            List<string> rowKindTargetsList = null;
            if (excelRowKindColNoList.Count > 0)
            {
                var strRowKinds = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_ROW_KIND_TARGETS, string.Empty);
                if (string.IsNullOrEmpty(strRowKinds))
                {
                    LoggerUxer.Error($"設定ファイルに行種類列の対象値が登録されていません。[キー:{KEY_EXCEL_ROW_KIND_TARGETS}]");
                    return false;
                }
                rowKindTargetsList = strRowKinds.Split(',').ToList();
            }
            if (excelAfWeCol == 0)
            {
                LoggerUxer.Info($"[AF/WE列:なし]");
            }
            else
            {
                LoggerUxer.Info($"[AF/WE列:{str}（番号:{excelAfWeCol}）]");
            }
            if (excelRowKindColNoList.Count == 0)
            {
                LoggerUxer.Info($"[行種類列:なし]");
            }
            else
            {
                LoggerUxer.Info($"[行種類列:{strK}（番号:{string.Join(",", excelRowKindColNoList)}）]");
            }

            if (!TryGetStartRow(sheet, iniDic, out var startRow))
            {
                LoggerUxer.Error($"開始行の取得に失敗しました。");
                return false;
            }

            // データ開始行から繰り返す
            var endJudgeCnt = 0;
            for (int row = startRow; ; row++)
            {
                // データ行終了値が見つからない
                if (row > 3000)
                {
                    LoggerUxer.Error($"データ終了判定値が見つかりません。[{KEY_EXCEL_DATA_END_JUDGE_COL}:{excelDataEndJudgeCol},{KEY_EXCEL_DATA_END_JUDGE_VALUE}:{excelDataEndJudgeValue}]");
                    return false;
                }

                // データ行終了判定
                var judgeValue = XlUxer.GCell(sheet, row, excelDataEndJudgeCol).GetFormattedString().Trim();

                //if (string.IsNullOrEmpty(judgeValue))
                //    LoggerUxer.Info("ここよ💖");
                if (row == 402)
                    LoggerUxer.Info("ここよ💖");

                if (StringUxer.WildcardMatch(
                    judgeValue,
                    excelDataEndJudgeValue))
                {
                    if (dataEndRetryCount > 0)
                    {
                        endJudgeCnt++;

                        if (endJudgeCnt > dataEndRetryCount)
                        {
                            // おわり
                            LoggerUxer.Info($"[データ行終了判定値検出行：{row},判定値:{judgeValue},連続回数:{endJudgeCnt}]");
                            break;
                        }
                        LoggerUxer.Info($"[データ行終了判定値検出行：{row},判定値:{judgeValue},連続回数:{endJudgeCnt}]");
                        continue;
                    }
                    LoggerUxer.Info($"[データ行終了判定値検出行：{row},判定値:{judgeValue}]");
                    break;
                }
                else
                {
                    endJudgeCnt = 0;
                }

                if (sheet.Row(row).IsHidden)
                {
                    // 非表示行の時
                    switch (hiddenRowHandlinng.ToLower())
                    {
                        case HIDDEN_ROW_HANDLING_SKIP:
                            // スキップ
                            continue;
                        case HIDDEN_ROW_HANDLING_INCLUDE:
                            // 対象
                            break;
                        case HIDDEN_ROW_HANDLING_WARN:
                            // 警告（ログ＋スキップ）
                            LoggerUxer.Warn($"非表示行をスキップします。[行番号:{row}]");
                            continue;
                        default:
                            // 無効値
                            LoggerUxer.Error($"設定ファイルの非表示行の扱いに無効な値が設計されています。[キー:{KEY_EXCEL_HIDDEN_ROW_HANDLING},値:{hiddenRowHandlinng}]");
                            return false;
                    }
                }

                // AF/WE判定列が空の行は飛ばす
                if (excelAfWeCol > 0 &&
                    string.IsNullOrEmpty(XlUxer.GCell(sheet, row, excelAfWeCol).GetFormattedString()))
                    continue;

                // 行種類が対象に含まれない行は飛ばす
                var rowKind = string.Empty;
                //var isRowKindFirst = false;
                //var isRowKindLast = false;
                if (excelRowKindColNoList.Count > 0)
                {
                    // 複数列指定対応
                    foreach (var col in excelRowKindColNoList)
                    {
                        rowKind += XlUxer.GCell(sheet, row, col).GetFormattedString().Trim();
                    }

                    // 改行除去
                    rowKind = StringUxer.CutKaigyo(rowKind);

                    if (string.IsNullOrEmpty(rowKind)) continue;
                    if (!rowKindTargetsList.Contains(rowKind)) continue;

                    // 👇未使用のため削除
                    //isRowKindFirst = rowKindTargetsList.IndexOf(rowKind) == 0;
                    //isRowKindLast = rowKindTargetsList.IndexOf(rowKind) == (rowKindTargetsList.Count - 1);
                }

                // 日毎値
                var targetDateList = dateDic
                    .Where(m => m.Value >= fromDate)
                    .OrderBy(m => m.Key)
                    .ToList();
                foreach (var kvp in targetDateList)
                {
                    if (sheet.Cell(row, kvp.Key).TryGetValue<int>(out var intValue) &&
                        intValue != 0)
                    {
                        // 数値の値があるとき（あっても0以外）のみデータ作成
                        
                        // 項目データ取得
                        if (!TryGetEasyItems(sheet, row, cdList, kvp.Value, intValue, excelSettings, out var items, out var messageForMe))
                        {
                            LoggerUxer.Error($"Eazy項目値取得に失敗しました。[エラー詳細:{messageForMe}]");
                            return false;
                        }

                        // リストに追加
                        outList.Add(items);
                    }
                }
            }
            return true;
        }

        private static bool TryGetEasyItems(
            IXLWorksheet sheet,
            int row,
            List<ColumnDefinition> cdList,
            DateTime date,
            int su,
            ExcelSettingsDto excelSettings,
            out string[] items,
            out string messageForMe)
        {
            items = new string[cdList.Count];
            messageForMe = string.Empty;

            for (int cnt = 0; cnt < cdList.Count; cnt++)
            {
                //if (cnt == 5)
                //    LoggerUxer.Info("きたよ💖");

                var item = string.Empty;

                switch(cdList[cnt].Type)
                {
                    case ColumnType.Fixed:
                        item = cdList[cnt].FixedValue;
                        break;
                    case ColumnType.Mapping:
                        if (!ExcelCellValidatorUxer.IsValidA1Format(cdList[cnt].TargetCol + "1"))
                        {
                            messageForMe = $"列レターが不正です。[項目名:{cdList[cnt].LogicalName},列レター値:{cdList[cnt].TargetCol}]";
                            return false;
                        }
                        var col = ExcelCellValidatorUxer.ColumnNameToNumber(cdList[cnt].TargetCol);
                        item = XlUxer.GCell(sheet, row, col).GetFormattedString().Trim();
                        break;
                    case ColumnType.Conversion:
                    case ColumnType.FileConversion:
                        if (!ExcelCellValidatorUxer.IsValidA1Format(cdList[cnt].TargetCol + "1"))
                        {
                            messageForMe = $"列レターが不正です。[項目名:{cdList[cnt].LogicalName},列レター値:{cdList[cnt].TargetCol}]";
                            return false;
                        }
                        col = ExcelCellValidatorUxer.ColumnNameToNumber(cdList[cnt].TargetCol);
                        var key = XlUxer.GCell(sheet, row, col).GetFormattedString().Trim(); // スペース値のみのとき空文字とする
                        key = key.Replace("\r", "").Replace("\n", "");

                        // まずは標準辞書から
                        item = DictionaryUxer.GetValueOrDefault(
                            cdList[cnt].ConvertMap, key, string.Empty);

                        if (string.IsNullOrEmpty(item))
                        {
                            // ないとき、Excel辞書から
                            if (excelSettings.KVPDic.ContainsKey(cdList[cnt].LogicalName))
                            {
                                var dic = excelSettings.KVPDic[cdList[cnt].LogicalName];
                                item = DictionaryUxer.GetValueOrDefault(
                                    dic, key, string.Empty);
                            }
                        }

                        if (string.IsNullOrEmpty(item))
                        {
                            messageForMe = $"変換値が取得できませんでした。[項目名:{cdList[cnt].LogicalName},セル値:{key}]";
                            return false;
                        }
                        break;
                    case ColumnType.FuncReplace:
                        var str = cdList[cnt].FixedValue.Trim().Trim('(').Trim(')').Trim();
                        string[] delimita = new string[] { "→"};
                        var part = str.Split(delimita, StringSplitOptions.None);
                        if (part.Length != 2)
                        {
                            messageForMe = $"関数指定内容が不正です。[項目名:{cdList[cnt].LogicalName},関数指定内容:{cdList[cnt].FixedValue}]";
                            return false;
                        }
                        var letter = cdList[cnt].TargetCol.Trim();
                        if (!ExcelCellValidatorUxer.IsValidA1Format(letter + "1"))
                        {
                            messageForMe = $"関数指定値の列が不正です。[項目名:{cdList[cnt].LogicalName},列レター値:{cdList[cnt].TargetCol}]";
                            return false;
                        }
                        col = ExcelCellValidatorUxer.ColumnNameToNumber(letter);
                        var text = XlUxer.GCell(sheet, row, col).GetFormattedString().Trim();
                        item = text.Replace(part[0].Trim('\"'), part[1].Trim('\"'));
                        break;

                    case ColumnType.FuncSubstring:
                        str = cdList[cnt].FixedValue.Trim().Trim('(').Trim(')').Trim();
                        part = str.Split(',');
                        if (part.Length != 2)
                        {
                            messageForMe = $"関数指定内容が不正です。[項目名:{cdList[cnt].LogicalName},関数指定内容:{cdList[cnt].FixedValue}]";
                            return false;
                        }
                        letter = cdList[cnt].TargetCol.Trim();
                        if (!ExcelCellValidatorUxer.IsValidA1Format(letter + "1"))
                        {
                            messageForMe = $"関数指定値の列が不正です。[項目名:{cdList[cnt].LogicalName},列レター値:{cdList[cnt].TargetCol}]";
                            return false;
                        }
                        col = ExcelCellValidatorUxer.ColumnNameToNumber(letter);
                        text = XlUxer.GCell(sheet, row, col).GetFormattedString().Trim();

                        if (!int.TryParse(part[0].Trim(), out var intStartKeta))
                        {
                            messageForMe = $"関数指定値の開始桁が不正です。[項目名:{cdList[cnt].LogicalName},開始位置:{part[0].Trim()}]";
                            return false;
                        }

                        if (!int.TryParse(part[1].Trim(), out var intKetasu))
                        {
                            messageForMe = $"関数指定値の桁数が不正です。[項目名:{cdList[cnt].LogicalName},開始位置:{part[1].Trim()}]";
                            return false;
                        }

                        item = text.Substring(intStartKeta - 1, intKetasu);
                        break;

                    case ColumnType.HorizontalDate:
                        item = date.ToString("yyyy/MM/dd");
                        break;
                    case ColumnType.ValueAmount:
                        if (!string.IsNullOrEmpty(cdList[cnt].FixedValue) &&
                            !string.IsNullOrEmpty(cdList[cnt].TargetCol))
                        {
                            var valueList = cdList[cnt].FixedValue.Split('|').ToList();
                            var colList = cdList[cnt].TargetCol.Split('+').ToList();
                            var text2 = string.Empty;
                            foreach(var col2 in colList)
                            {
                                if (!XlUxer.TryConvertColLetterToNumber(col2, out var _))
                                {
                                    messageForMe = $"列レターが不正です。[項目名:{cdList[cnt].LogicalName},列レター:{cdList[cnt].TargetCol}]";
                                    return false;
                                }
                                text2 += XlUxer.GCell(sheet, col2 + row).GetFormattedString().Trim();
                            }
                            text2 = StringUxer.CutKaigyo(text2);

                            if (valueList.Contains(text2))
                            {
                                item = su.ToString();
                            }
                            else
                            {
                                item = string.Empty;
                            }
                        }
                        else
                        {
                            item = su.ToString();
                        }
                        break;
                    case ColumnType.LineId:
                        if (string.IsNullOrEmpty(cdList[cnt].FixedValue))
                        {
                            messageForMe = $"ラインIDが取得されていません。[項目名:{cdList[cnt].LogicalName}]";
                            return false;
                        }
                        item = cdList[cnt].FixedValue;
                        break;
                    case ColumnType.ModuleId:
                        if (string.IsNullOrEmpty(cdList[cnt].FixedValue))
                        {
                            messageForMe = $"モジュールIDが取得されていません。[項目名:{cdList[cnt].LogicalName}]";
                            return false;
                        }
                        item = cdList[cnt].FixedValue;
                        break;
                }
                items[cnt] = item;
            }
            return true;
        }

        ///// <summary>
        ///// タクト管理データ取得
        ///// </summary>
        ///// <param name="sheet">シート</param>
        ///// <param name="lineInfo">ライン情報</param>
        ///// <param name="row">行</param>
        ///// <param name="iniDic">INI辞書</param>
        ///// <param name="list">リスト</param>
        ///// <param name="tactIndex">[出力]タクトインデックス</param>
        ///// <returns>成否</returns>
        //private static bool GetTactManagementFromRow(IXLWorksheet sheet, ExcelLineDto lineInfo, int row, ref List<TactManagementDto> list, out int tactIndex)
        //{

        //    // 基本部番
        //    var kihonBuban = CreateBuban(CutKaigyo(XlUxer.GCell(sheet, row, COL_BUBAN).GetFormattedString()));

        //    // 機種コード
        //    var modelCd = XlUxer.GCell(sheet, row, COL_MODEL_CD).GetFormattedString();
        //    if (string.IsNullOrEmpty(modelCd))
        //    {
        //        // 空のとき、既存リスト内の同一部番があればそこから取得
        //        var dto = list
        //            .FirstOrDefault(m => m.BasicPartsNo == kihonBuban);
        //        if (dto != null)
        //        {
        //            modelCd = dto.ModelCd;
        //        }
        //        else
        //        {
        //            // ないとき、上の行を探る！
        //            for (var rowTmp = row - 1; rowTmp >= 1; rowTmp--)
        //            {
        //                var str = XlUxer.GCell(sheet, rowTmp, COL_MODEL_CD).GetFormattedString();
        //                if (!string.IsNullOrEmpty(str))
        //                {
        //                    modelCd = str;
        //                    break;
        //                }
        //            }
        //            if (string.IsNullOrEmpty(modelCd)) modelCd = "(No Model code!)";
        //        }
        //    }

        //    // 派生
        //    var delivation = XlUxer.GCell(sheet, row, COL_DERIVATION).GetFormattedString();

        //    // タクト
        //    var tact = 0;
        //    if(XlUxer.GCell(sheet, row, COL_TACT).TryGetValue<int>(out var t))
        //    {
        //        tact = t;
        //    }

        //    // リストに追加しタクトインデックス取得
        //    tactIndex = TactManagementDto.AddToList(list, new TactManagementDto()
        //    {
        //        LineId = lineInfo.LineId,
        //        ModelCd = modelCd,
        //        BasicPartsNo = kihonBuban,
        //        Derivation = delivation,
        //        Tact = tact,
        //    });
        //    return true;
        //}

        /// <summary>
        /// 改行コードカット
        /// </summary>
        /// <param name="str">文字列</param>
        /// <returns>カット後文字列</returns>
        private static string CutKaigyo(string str)
        {
            return str
                .Replace("\r\n", "")
                .Replace("\r", "")
                .Replace("\n", "");
        }

        /// <summary>
        /// 部番作成
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private static string CreateBuban(string str)
        {
            // 謎の全角ハイフン
            str = str.Replace("‐", "-");

            // スペースも稀にあり！
            str = str.Replace("　", "");

            // 中のスペースは残し、両端のスペースをカット
            //str = str.Replace(" ", "");
            str = str.Trim();

            // 後ろのスペースは不要！
            //return (str.Replace("-", " ") + "               ").Substring(0, 15);
            return str.Replace("-", " ").Trim();
        }

        /// <summary>
        /// アイテムと列レターのマッピング辞書
        /// </summary>
        private static Dictionary<string, string> ItemColumnMappingDic = null;

        private const string INI_KEY_EXCEL_KIHON_BUBAN_GYOSU = "ExcelKihonBubanGyosu";
        private const string INI_KEY_EXCEL_SHIMUKE_TACT_ROW_ZURE = "ExcelShimukeTactRowZure";

        // WE生産計画詳細データ取得
        private static bool GetWeOrderFromSheet(
            IXLWorksheet sheet,
            Dictionary<string, string> iniDic,
            ExcelLineDto lineInfo,
            DateTime now,
            int row, 
            Dictionary<int, DateTime> dateDic, 
            DateTime fromDate, 
            int afWeCol, 
            int mcSeisanFukaRow, 
            List<WeOrderDto> list)
        {
            //if (int.TryParse(strAfWeCol, out var afWeCol) == false || afWeCol < 1)
            //{
            //    LoggerUxer.Error($"設定ファイルのAE/WE列番号の設定値が不正です。[キー:{KEY_EXCEL_AF_WE_COL},値:{strAfWeCol}]");
            //    return false;
            //}

            //if (int.TryParse(strAfWeCol, out var afWeCol) == false || afWeCol < 1)
            //{
            //    LoggerUxer.Error($"設定ファイルのAE/WE列番号の設定値が不正です。[キー:{KEY_EXCEL_AF_WE_COL},値:{strAfWeCol}]");
            //    return false;
            //}

            //int COL_BUBAN = lineInfo.LineCol;
            //int COL_MODEL_CD = COL_BUBAN - 1;
            //int COL_DERIVATION = COL_BUBAN + 1;
            //int COL_TACT = COL_DERIVATION + 1;

            // メンバ名前取得用
            var _dto_ = new WeOrderDto();

            // プロパティのExcelレター登録有無チェック
            if (string.IsNullOrEmpty(DictionaryUxer.GetValueOrDefault(ItemColumnMappingDic, nameof(_dto_.BasicPartsNo), string.Empty)))
            {
                LoggerUxer.Error($"プロパティに対してExcel列レターが登録されていません。[プロパティ:{nameof(_dto_.BasicPartsNo)},登録済みのプロパティ:{string.Join(",", ItemColumnMappingDic.Keys)}]");
                return false;
            }
            if (string.IsNullOrEmpty(DictionaryUxer.GetValueOrDefault(ItemColumnMappingDic, nameof(_dto_.ModelCd), string.Empty)))
            {
                LoggerUxer.Error($"プロパティに対してExcel列レターが登録されていません。[プロパティ:{nameof(_dto_.ModelCd)},登録済みのプロパティ:{string.Join(",", ItemColumnMappingDic.Keys)}]");
                return false;
            }
            if (string.IsNullOrEmpty(DictionaryUxer.GetValueOrDefault(ItemColumnMappingDic, nameof(_dto_.Derivation), string.Empty)))
            {
                LoggerUxer.Error($"プロパティに対してExcel列レターが登録されていません。[プロパティ:{nameof(_dto_.Derivation)},登録済みのプロパティ:{string.Join(",", ItemColumnMappingDic.Keys)}]");
                return false;
            }
            if (string.IsNullOrEmpty(DictionaryUxer.GetValueOrDefault(ItemColumnMappingDic, nameof(_dto_.TactTime), string.Empty)))
            {
                LoggerUxer.Error($"プロパティに対してExcel列レターが登録されていません。[プロパティ:{nameof(_dto_.TactTime)},登録済みのプロパティ:{string.Join(",", ItemColumnMappingDic.Keys)}]");
                return false;
            }
            // 派生（区分）とタクトの行ずれ数
            var haseiTactZure = int.TryParse(
                DictionaryUxer.GetValueOrDefault(
                    iniDic, INI_KEY_EXCEL_SHIMUKE_TACT_ROW_ZURE, "0"), out var intVal0)
                ? intVal0 : 0;

            // 基本部番行数
            var kihonBubanGyosu =
                int.TryParse(
                    DictionaryUxer.GetValueOrDefault(iniDic, INI_KEY_EXCEL_KIHON_BUBAN_GYOSU, "1"),
                    out var intVal) ? intVal : 1;

            // 基本部番列番号
            var colNo = XlUxer.GetColNo(ItemColumnMappingDic[nameof(_dto_.BasicPartsNo)]);
            // 基本部番
            var kihonBuban = string.Empty;
            for (var cnt = 0; ; cnt++)
            {
                var rowTmp = row - cnt;
                kihonBuban = XlUxer.GCell(sheet, rowTmp, colNo).GetFormattedString();
                if (kihonBubanGyosu >= 2) kihonBuban += XlUxer.GCell(sheet, rowTmp + 1, colNo).GetFormattedString();
                if (kihonBubanGyosu >= 3) kihonBuban += XlUxer.GCell(sheet, rowTmp + 2, colNo).GetFormattedString();
                kihonBuban = CreateBuban(CutKaigyo(kihonBuban));
                // とりあえず長さが10以上であれば連結成功とみなす！
                if (kihonBuban.Trim().Length >= 10) break;
                if (cnt >= 10)
                {
                    LoggerUxer.Error($"基本部番の取得に失敗しました。[シート名:{sheet.Name},行:{row}]");
                    return false;
                }
            }

            // 機種コード列（レター）
            var letter = ItemColumnMappingDic[nameof(_dto_.ModelCd)];

            // 機種コード
            var modelCd = string.Empty;
            if (letter.IndexOf(",") >= 0)
            {
                // 漁法モード
                var strSplit = letter.Split(',');
                var gyohoName = strSplit[0].Trim();
                switch (gyohoName)
                {
                    case "別シート":
                        // バリデ済みのためここではバリデしない
                        var sheetName = strSplit[1].Trim();
                        var bubanColLetter = strSplit[2].Trim();
                        var kishuColLetter = strSplit[3].Trim();
                        var startRow = int.Parse(strSplit[4].Trim());
                        var book = sheet.Workbook;
                        if (!book.Worksheets.Contains(sheetName))
                        {
                            // シート名なし
                            LoggerUxer.Error($"機種コードの漁法でシート名が存在しません。[シート名:{sheetName},キー名:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING}]");
                            return false;
                        }
                        var sheetTmp = book.Worksheet(sheetName);
                        const int KARA_GYO_MAX = 30;
                        var karaGyoCnt = 0;
                        for (var rowTmp = startRow; rowTmp < 10000; rowTmp++)
                        {
                            var buban = sheetTmp.Cell(bubanColLetter + rowTmp).GetFormattedString().Trim();

                            // 救済システム：1個左の列も探してみたりする…
                            var colNoQsai = XlUxer.GetColNo(bubanColLetter);
                            // 1列目だったら諦めましょう…
                            if (colNoQsai != 1) colNoQsai--;
                            var bubanQsai = sheetTmp.Cell(ExcelColumnHelperUxer.GetExcelColumnLetter(colNoQsai) + rowTmp).GetFormattedString().Trim();
                            // そしてそれは、ハイフン付きのままだったりする…
                            bubanQsai = bubanQsai.Replace("-", " ");

                            var kishu = sheetTmp.Cell(kishuColLetter + rowTmp).GetFormattedString().Trim();
                            if (string.IsNullOrEmpty(buban) && string.IsNullOrEmpty(kishu))
                            {
                                // 空行
                                karaGyoCnt++;
                                if (karaGyoCnt >= KARA_GYO_MAX)
                                {
                                    // 空行最大を超過👉別シートに登録なし
                                    LoggerUxer.Error($"機種コードの漁法で基本部番がシートに登録されていません。[シート名:{sheetName},基本部番:{kihonBuban},キー名:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING}]");
                                    return false;
                                }
                            }

                            if (buban.Length < 10) continue;

                            //LoggerUxer.Info($"[基本部番:{kihonBuban.Trim()},シート部番:{buban}]");
                            var kihonBuban_mae = kihonBuban.Trim().Substring(0, 8);
                            var kihonBuban_ato = kihonBuban.Trim().Substring(9);
                            //if (buban.IndexOf(kihonBuban.Trim()) >= 0 && !string.IsNullOrEmpty(kishu))
                            if (buban.Substring(0, 8) == kihonBuban_mae &&
                                buban.Substring(9).IndexOf(kihonBuban_ato) == 0 &&
                                !string.IsNullOrEmpty(kishu))
                            {
                                // 発見！（表では部番末尾にあと2桁ついていたりするので、部分一致で検索してます）
                                modelCd = kishu;
                                break;
                            }

                            if (bubanQsai.Length < 10) continue;

                            // 救済システム！左の列にあったりして…
                            //if (bubanQsai.IndexOf(kihonBuban.Trim()) >= 0 && !string.IsNullOrEmpty(kishu))
                            if (bubanQsai.Substring(0, 8) == kihonBuban_mae &&
                                bubanQsai.Substring(9).IndexOf(kihonBuban_ato) == 0 &&
                                !string.IsNullOrEmpty(kishu))
                            {
                                // 発見！
                                modelCd = kishu;
                                break;
                            }
                        }
                        if (string.IsNullOrEmpty(modelCd))
                        {
                            // 別シートに登録なし
                            LoggerUxer.Error($"機種コードの漁法で基本部番がシートに登録されていません。[シート名:{sheetName},基本部番:{kihonBuban},キー名:{KEY_EXCEL_ITEM_NAME_AND_COLUMN_LETTER_MAPPING}]");
                            return false;
                        }
                        break;
                }
            }
            else
            {
                // 列指定モード
                modelCd = XlUxer.GCell(sheet, row, XlUxer.GetColNo(letter)).GetFormattedString();
            }

            if (string.IsNullOrEmpty(modelCd))
            {
                // 空のとき、既存リスト内の同一部番があればそこから取得
                var dto = list
                    .FirstOrDefault(m => m.BasicPartsNo == kihonBuban);
                if (dto != null)
                {
                    modelCd = dto.ModelCd;
                }
                else
                {
                    // ないとき、上の行を探る！
                    if (ItemColumnMappingDic[nameof(_dto_.ModelCd)].IndexOf(",")>=0)
                    {
                        for (var rowTmp = row - 1; rowTmp >= 1; rowTmp--)
                        {
                            var str = XlUxer.GCell(sheet, rowTmp,
                                XlUxer.GetColNo(
                                    ItemColumnMappingDic[nameof(_dto_.ModelCd)])
                                ).GetFormattedString();
                            if (!string.IsNullOrEmpty(str))
                            {
                                modelCd = str;
                                break;
                            }
                        }
                    }
                    if (string.IsNullOrEmpty(modelCd)) modelCd = "(No Model code!)";
                }
            }

            // 派生
            var derivation = XlUxer.GCell(sheet, row + haseiTactZure,
                XlUxer.GetColNo(
                    ItemColumnMappingDic[nameof(_dto_.Derivation)])
                ).GetFormattedString();

            // 救済システム：1個上と2個上
            if (string.IsNullOrEmpty(derivation))
                derivation = XlUxer.GCell(sheet, row + haseiTactZure - 1,
                    XlUxer.GetColNo(
                        ItemColumnMappingDic[nameof(_dto_.Derivation)])
                    ).GetFormattedString();
            if (string.IsNullOrEmpty(derivation))
                derivation = XlUxer.GCell(sheet, row + haseiTactZure - 2,
                    XlUxer.GetColNo(
                        ItemColumnMappingDic[nameof(_dto_.Derivation)])
                    ).GetFormattedString();

            // タクト
            var tact = 0;
            //if (XlUxer.GCell(sheet, row + haseiTactZure,
            //    XlUxer.GetColNo(
            //        ItemColumnMappingDic[nameof(_dto_.TactTime)])
            //    ).TryGetValue<int>(out var t))
            //{
            //    tact = t;
            //}

            // 救済システム：1個上と2個上も見る
            var strT = string.Empty;
            if (string.IsNullOrEmpty(XlUxer.GCell(sheet, row + haseiTactZure,
                XlUxer.GetColNo(
                    ItemColumnMappingDic[nameof(_dto_.TactTime)])
                ).GetFormattedString()))
                if (string.IsNullOrEmpty(XlUxer.GCell(sheet, row + haseiTactZure - 1,
                    XlUxer.GetColNo(
                        ItemColumnMappingDic[nameof(_dto_.TactTime)])
                    ).GetFormattedString()))
                    strT = XlUxer.GCell(sheet, row + haseiTactZure - 2,
                    XlUxer.GetColNo(
                        ItemColumnMappingDic[nameof(_dto_.TactTime)])
                    ).GetFormattedString();
                else
                    strT = XlUxer.GCell(sheet, row + haseiTactZure - 1,
                    XlUxer.GetColNo(
                        ItemColumnMappingDic[nameof(_dto_.TactTime)])
                    ).GetFormattedString();
            else
                strT = XlUxer.GCell(sheet, row + haseiTactZure,
                XlUxer.GetColNo(
                    ItemColumnMappingDic[nameof(_dto_.TactTime)])
                ).GetFormattedString();
            int.TryParse(strT, out tact);

            // AF/WE値
            var afWe = sheet.Cell(row, afWeCol).GetFormattedString().Trim();
            var ueAfWe = sheet.Cell(row - 1, afWeCol).GetFormattedString().Trim();

            var targetDateList = dateDic
                .Where(m => m.Value >= fromDate)
                .OrderBy(m => m.Key)
                .ToList();
            foreach (var date in targetDateList)
            {
                if (sheet.Cell(row, date.Key).TryGetValue<int>(out var intValue) &&
                    intValue != 0)
                {
                    // 数値の値があるとき（あっても0以外）のみデータ作成
                    var weProductDate = date.Value;
                    var weAmount = 0;
                    var kdWeAmount = 0;
                    var afAmount = 0;
                    var kpsKintai = lineInfo.ShiftName;

                    int? productionLur = null;

                    if (mcSeisanFukaRow >= 1)
                    {
                        var str = sheet.Cell(mcSeisanFukaRow, date.Key).GetFormattedString();
                        str = str.Replace("%", "").Trim();
                        if (int.TryParse(str, out var intProductionLur) == false)
                        {
                            LoggerUxer.Warn($"MC生産負荷の取得に失敗しました。[日付:{date.Value},セル値:{sheet.Cell(mcSeisanFukaRow, date.Key).GetFormattedString()}]");
                            productionLur = -1;
                        }
                        else
                        {
                            productionLur = intProductionLur;
                        }
                    }

                    // 既存有無チェック
                    var kizonDto = list
                        .FirstOrDefault(
                            m =>
                                m.LineName == lineInfo.LineName &&
                                m.BasicPartsNo == kihonBuban &&
                                m.Derivation == derivation &&
                                m.WorkDate == weProductDate);

                    if ((afWe == "WE" && (ueAfWe == "KD" || derivation == "KD")) ||
                        (afWe == "CKD")) // CKDは新書式にて登場！
                    {
                        // KDのWEは別計上
                        afWe = "KDWE";
                    }

                    switch (afWe)
                    {
                        case "AF":
                        case "FUN":   // 新書式にて追加
                        case "SP":    // 新書式にて追加
                        case "MULTI": // 新書式にて追加
                            //case "KDWE":  // KDWEはAFとして登録
                            afAmount = intValue;

                            if (kizonDto != null)
                            {
                                // 既存あり→更新
                                if (kizonDto.AfAmount == 0)
                                {
                                    // 既存がゼロだったら更新
                                    kizonDto.AfAmount = afAmount;
                                    //LoggerUxer.Info($"★★★AFゼロ更新");
                                }
                                else if (kizonDto.AfAmount != afAmount)
                                {
                                    // 既存と値が異なる場合、既存優先（更新しない）
                                    LoggerUxer.Warn($"同一日同一コードでAF値が異なります（未更新）。[ライン名:{lineInfo.LineName},機種コード:{kihonBuban},日付:{weProductDate:yyyy/MM/dd},AF値①:{kizonDto.AfAmount},AF値②{afAmount}] ①のまま。");
                                }
                                else
                                {
                                    // 既存と値が同じ場合、何もしない
                                    //LoggerUxer.Info($"★★★AF同値[{afAmount}]");
                                }
                            }
                            else
                            {
                                // 追加
                                list.Add(new WeOrderDto()
                                {
                                    CreateRecordDate = now,
                                    LineId = lineInfo.LineId,
                                    LineName = lineInfo.LineName,
                                    BasicPartsNo = kihonBuban,
                                    WorkDate = weProductDate,
                                    AfAmount = afAmount,
                                    WeAmount = weAmount,
                                    KdWeAmount = kdWeAmount,
                                    ProductionLur = productionLur,
                                    ModelCd = modelCd,
                                    Derivation = derivation,
                                    TactTime = tact,
                                });
                            }
                            break;

                        case "WE":
                            // WE
                            weAmount = intValue;
                            if (kizonDto != null)
                            {
                                // 既存あり→更新
                                if (kizonDto.WeAmount == 0)
                                {
                                    // 既存がゼロだったら更新
                                    kizonDto.WeAmount = weAmount;
                                    //LoggerUxer.Info($"★★★WEゼロ更新");
                                }
                                else if (kizonDto.WeAmount != weAmount)
                                {
                                    // 既存と値が異なる場合、既存優先（更新しない）
                                    LoggerUxer.Warn($"同一日同一コードでWE値が異なります（未更新）。[ライン名:{lineInfo.LineName},機種コード:{kihonBuban},日付:{weProductDate:yyyy/MM/dd},WE値①:{kizonDto.WeAmount},WE値②{weAmount}] ①のまま。");
                                }
                                else
                                {
                                    // 既存と値が同じ場合、何もしない
                                    //LoggerUxer.Info($"★★★WE同値[{weAmount}]");
                                }
                            }
                            else
                            {
                                // 追加
                                list.Add(new WeOrderDto()
                                {
                                    CreateRecordDate = now,
                                    LineId = lineInfo.LineId,
                                    LineName = lineInfo.LineName,
                                    BasicPartsNo = kihonBuban,
                                    WorkDate = weProductDate,
                                    AfAmount = afAmount,
                                    WeAmount = weAmount,
                                    KdWeAmount = kdWeAmount,
                                    ProductionLur = productionLur,
                                    ModelCd = modelCd,
                                    Derivation = derivation,
                                    TactTime = tact,
                                });
                            }
                            break;

                        case "KDWE":
                            // KDWE
                            kdWeAmount = intValue;
                            // ※注意：最終的なテーブル項目へのマッピングはINSERT-SQLに委ねる。

                            if (kizonDto != null)
                            {
                                // 既存あり→更新
                                if (kizonDto.KdWeAmount == 0)
                                {
                                    // 既存がゼロだったら更新
                                    kizonDto.KdWeAmount = kdWeAmount;
                                }
                                else if (kizonDto.KdWeAmount != kdWeAmount)
                                {
                                    // 既存と値が異なる場合、既存優先（更新しない）
                                    LoggerUxer.Warn($"同一日同一コードでKDWE値が異なります（未更新）。[ライン名:{lineInfo.LineName},機種コード:{kihonBuban},日付:{weProductDate:yyyy/MM/dd},WE値①:{kizonDto.KdWeAmount},WE値②{kdWeAmount}] ①のまま。");
                                }
                                else
                                {
                                    // 既存と値が同じ場合、何もしない
                                }
                            }
                            else
                            {
                                // 追加
                                list.Add(new WeOrderDto()
                                {
                                    CreateRecordDate = now,
                                    LineId = lineInfo.LineId,
                                    LineName = lineInfo.LineName,
                                    BasicPartsNo = kihonBuban,
                                    WorkDate = weProductDate,
                                    AfAmount = afAmount,
                                    WeAmount = weAmount,
                                    KdWeAmount = kdWeAmount,
                                    ProductionLur = productionLur,
                                    ModelCd = modelCd,
                                    Derivation = derivation,
                                    TactTime = tact,
                                });
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// Excelの「MC生産負荷(2交替)」の行番号取得
        /// </summary>
        /// <param name="sheet">シート</param>
        /// <param name="iniDic">INIDIC</param>
        /// <param name="retRow">[出力]行番号（無効の場合-1）</param>
        /// <returns>成否</returns>
        private static bool GetMcSeisanFukaRow(IXLWorksheet sheet, Dictionary<string, string> iniDic, out int retRow)
        {
            const string KEY_EXCEL_MC_SEISAN_FUKA_ENABLED = "ExcelMcSeisanFukaEnabled";
            const string KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_COL = "ExcelMcSeisanFukaJudgeCol";
            const string KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_VALUE_PATTERN = "ExcelMcSeisanFukaJudgeValuePattern";

            retRow = -1;

            var str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_MC_SEISAN_FUKA_ENABLED, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"MC生産負荷有効無効指定が未設定です。[キー:{KEY_EXCEL_MC_SEISAN_FUKA_ENABLED}]");
                return false;
            }

            if (!bool.TryParse(str, out var isMcSeisanFukaEnabled))
            {
                LoggerUxer.Error($"MC生産負荷有効無効指定が無効値です。[キー:{KEY_EXCEL_MC_SEISAN_FUKA_ENABLED},値:{str}]");
                return false;
            }

            if (!isMcSeisanFukaEnabled)
            {
                LoggerUxer.Info($"MC生産負荷は無効です。処理を継続します。");
                return true;
            }

            str = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_COL, string.Empty);
            if (string.IsNullOrEmpty(str))
            {
                LoggerUxer.Error($"MC生産負荷判定列が未設定です。[キー:{KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_COL}]");
                return false;
            }
            if (!XlUxer.TryConvertColLetterToNumber(str, out var mcSeisanFukaRowJudgeCol))
            {
                LoggerUxer.Error($"MC生産負荷判定列の設定値が不正です。[{KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_COL}:{str}]");
                return false;
            }
            LoggerUxer.Info($"[MC生産負荷判定列:{str}（番号:{mcSeisanFukaRowJudgeCol}）]");

            var mcSeisanFukaRowJudgeValuePattern = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_VALUE_PATTERN, string.Empty);
            if (string.IsNullOrEmpty(mcSeisanFukaRowJudgeValuePattern))
            {
                LoggerUxer.Error($"設定ファイルのMC生産負荷行判定値パターンが未設定です。[キー:{KEY_EXCEL_MC_SEISAN_FUKA_JUDGE_VALUE_PATTERN}]");
                return false;
            }

            for (int row = 1; row <= 1000; row++)
            {
                // ワイルドカードマッチング
                if (StringUxer.WildcardMatch(
                    sheet.Cell(row, mcSeisanFukaRowJudgeCol).GetFormattedString().Trim(),
                    mcSeisanFukaRowJudgeValuePattern))
                {
                    retRow = row;
                    return true;
                }
            }
            LoggerUxer.Error($"設定ファイルのMC生産負荷行が見つかりませんでした。[列:{mcSeisanFukaRowJudgeCol},判定値パターン:{mcSeisanFukaRowJudgeValuePattern}]");
            return false;
        }
        private static bool TryGetYmFromText(string text, out DateTime? dt)
        {
            dt = null;

            if (text.IndexOf("年") >= 0 && text.IndexOf("月") >= 0)
            {
                var pos = text.IndexOf("年");
                var strNen = text.Substring(0, pos);
                text = text.Substring(pos + "年".Length);
                pos = text.IndexOf("月");
                var strTsuki = text.Substring(0, pos);
                // 2. TryParseを実行（成功したらdtに代入、失敗ならnullのまま）
                if (DateTime.TryParse($"{strNen}/{strTsuki}/01", out var tempDt))
                {
                    dt = tempDt; // ここで DateTime? に代入
                    return true;
                }
                else
                {
                    dt = null;
                    return false;
                }
            }
            else if (text.IndexOf("/") >= 0)
            {
                var pos = text.IndexOf("/");
                var strNen = text.Substring(0, pos);
                text = text.Substring(pos + "/".Length);
                pos = text.IndexOf("/");
                var strTsuki = string.Empty;
                if (pos < 0)
                {
                    strTsuki = text;
                }
                else
                {
                    strTsuki = text.Substring(pos + "/".Length);
                }
                // 2. TryParseを実行（成功したらdtに代入、失敗ならnullのまま）
                if (DateTime.TryParse($"{strNen}/{strTsuki}/01", out var tempDt))
                {
                    dt = tempDt; // ここで DateTime? に代入
                    return true;
                }
                else
                {
                    dt = null;
                    return false;
                }
            }
            else return false;
        }

        private static int RunOneFileEazy(
            string path,
            DateTime now,
            List<ExcelSheetCheckDto> checkItems,
            Dictionary<string, string> iniDic,
            ExcelSettingsDto excelSettings,
            DateTime fromDate,
            DateTime? toDate,
            LineIdHelper lineIdHelper,
            List<ColumnDefinition> cdList,
            List<string[]> outList)
        {
            // INIキー定義
            const string KEY_INCLUDED_SHEET_NAMES = "IncludedSheetNames";
            const string KEY_EXCLUDED_SHEET_NAMES = "ExcludedSheetNames";

            if (System.IO.Path.GetExtension(path).ToLower() != ".xlsx" &&
                System.IO.Path.GetExtension(path).ToLower() != ".xlsm")
            {
                LoggerUxer.Warn($"拡張子が異なるためスキップします。[{path}]");
                return (int)Common.RetCode.Success;
            }

            LoggerUxer.Info($"[ファイル:{path}]");

            try
            {
                //using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                //using (var book = new XLWorkbook(fs))
                using (var book = LoadWorkbookWithoutPhoneticError(path))
                {
                    // 処理対象のシート名
                    var incSheetsCsv = DictionaryUxer.GetValueOrDefault(iniDic, KEY_INCLUDED_SHEET_NAMES, string.Empty);
                    if (!string.IsNullOrEmpty(incSheetsCsv))
                    {
                        LoggerUxer.Info($"[処理対象シート名:{incSheetsCsv}]");
                    }
                    else
                    {
                        LoggerUxer.Info($"[処理対象シート名:なし]");
                    }

                    // 処理対象外のシート名
                    var excSheetsCsv = DictionaryUxer.GetValueOrDefault(iniDic, KEY_EXCLUDED_SHEET_NAMES, string.Empty);
                    if (!string.IsNullOrEmpty(excSheetsCsv))
                    {
                        if (string.IsNullOrEmpty(incSheetsCsv))
                        {
                            LoggerUxer.Info($"[処理対象外シート名:{excSheetsCsv}]");
                        }
                        else
                        {
                            LoggerUxer.Warn($"処理対象シート名が指定されているため、処理対象外指定は無効になります。[キー:{KEY_EXCLUDED_SHEET_NAMES},値:{excSheetsCsv}]");
                        }
                    }
                    else
                    {
                        LoggerUxer.Info($"[処理対象外シート名:なし]");
                    }
                    // シートフィルタ作成
                    var incSheets = string.IsNullOrEmpty(incSheetsCsv) ? new string[] { } : incSheetsCsv.Split(',');
                    var excSheets = string.IsNullOrEmpty(excSheetsCsv) ? new string[] { } : excSheetsCsv.Split(',');
                    var sheetFilter = new SheetFilterUxer(incSheets, excSheets);

                    // シート右端から左へループ
                    for (int sheetIndex = book.Worksheets.Count; sheetIndex >= 1; sheetIndex--)
                    {
                        // シート取得
                        var sheet = book.Worksheet(sheetIndex);

                        if (!sheetFilter.CanPass(sheet.Name.Trim()))
                        {
                            // シート名フィルタに引っかからなかった時、飛ばす
                            LoggerUxer.Info($"対象外のシートをスキップします。[シート名:{sheet.Name}]");
                            continue;
                        }

                        // 条件を満たさないシートは飛ばす
                        if (CheckExcelSheet(sheet, checkItems) == false)
                        {
                            LoggerUxer.Info($"シート条件を満たさないためスキップします。[シート名:{sheet.Name}]");
                            continue;
                        }

                        LoggerUxer.Info($"[シート名:{sheet.Name}]");

                        // ラインID取得
                        if (!TryGetLineIdFromCell(sheet, iniDic, out var id))
                        {
                            return (int)Common.RetCode.ExcelLinesInfoGetError;
                        }
                        if (id > 0)
                        {
                            LoggerUxer.Info($"[ラインID:{id}]");
                        }
                        lineIdHelper.SheetLineId = id;

                        // 列定義にラインIDを入れる
                        SetLineIdToColumnDifinition(lineIdHelper.GetCurrentLineId(), cdList);

                        // モジュールID取得
                        var moduleId = -1;
                        if (lineIdHelper.GetCurrentLineId() > 0 &&
                            lineIdHelper.GetCurrentModuleId() < 0)
                        {
                            if (!GetModuleIdFromLineId(lineIdHelper.GetCurrentLineId(), out moduleId,out var messageForMe))
                            {
                                LoggerUxer.Error($"モジュールIDの取得に失敗しました。[ラインID:{lineIdHelper.GetCurrentLineId()},詳細:{messageForMe}]");
                                return (int)Common.RetCode.ExcelOneFileException;
                            }
                        }
                        if (moduleId > 0)
                        {
                            LoggerUxer.Info($"[モジュールID:{moduleId}]");
                        }

                        lineIdHelper.SheetModuleId = moduleId;

                        // 列定義にモジュールIDを入れる
                        SetModuleIdToColumnDifinition(moduleId, cdList);

                        // 日付情報取得
                        if (!GetDateList(sheet, fromDate, toDate, iniDic, out var dateDic))
                        {
                            return (int)Common.RetCode.ExcelDateCellGetError;
                        }

                        // 最大日が開始日より小さい場合、シートを飛ばす
                        if (dateDic.Count > 0 && dateDic.Values.Max() < fromDate)
                        {
                            LoggerUxer.Info($"シート最大日が開始日より前のためスキップします。[シート最大日:{(DateTime)(dateDic.Values.Max()):yyyy/MM/dd}]");
                            continue;
                        }

                        // データ取得
                        if (!GetDataEazy(sheet, now, dateDic, fromDate, iniDic, cdList, excelSettings, outList))
                        {
                            return (int)Common.RetCode.ExcelDataGetError;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LoggerUxer.Error($"1ファイル処理にてエラーが発生しました。[{ex.Message}]");
                return (int)Common.RetCode.ExcelOneFileException;
            }
            return (int)Common.RetCode.Success;
        }

        // ライン判別とINI更新
        private static bool LineHanteiFromFilePathAndReloadIni(
            string path,
            Dictionary<string,string> iniDic,
            string sectionName,
            out int lineId,
            out ExcelSettingsDto excelSettings,
            out string resultMessage)
        {
            lineId = -1;
            excelSettings = null;
            resultMessage = string.Empty;

            if (!TryGetLineIdFromFilePath(path, iniDic, out lineId))
            {
                resultMessage = "ファイル名からラインID取得に失敗しました。";
                return false;
            }
            if (lineId == -1)
            {
                LoggerUxer.Info("ファイル名からラインIDは取得しませんでした。");
                return true;
            }

            // Excel設定のロード
            if (!LoadExcelSetting(iniDic, lineId, out excelSettings))
            {
                return false;
            }
            // INI読みに反映
            IniHelper.IniList = excelSettings.IniList;

            // INI取得しなおし（Excel設定で変化する）
            foreach (var key in iniDic.Keys.ToList())
                iniDic[key] = IniHelper.ReadValue(Common.INI_FILE_PATH, sectionName, key);
            foreach (var iniDto in excelSettings.IniList
                .Where(m => m.Section == sectionName)
                .ToList())
                iniDic[iniDto.Key] = IniHelper.ReadValue(Common.INI_FILE_PATH, sectionName, iniDto.Key);
            return true;
        }

        /// <summary>
        /// ラインIDからモジュールIDを取得する
        /// </summary>
        /// <param name="lineId"></param>
        /// <param name="moduleId"></param>
        /// <param name="resultMessage"></param>
        /// <returns></returns>
        private static bool GetModuleIdFromLineId(
            int lineId,
            out int moduleId,
            out string resultMessage)
        {
            resultMessage = string.Empty;
            moduleId = -1;
            var path = ".\\LineIdModuleId.csv";
            if (!CsvUxer.TryGetDictionaryFromCsv(path, out var dic,out var messageForMe))
            {
                resultMessage = $"モジュールIDの取得に失敗しました。[詳細:{messageForMe}]";
                return false;
            }
            if (dic.ContainsKey(lineId.ToString()))
            {
                moduleId = int.TryParse(dic[lineId.ToString()], out var intVal) ? intVal : moduleId;
            }
            return true;
        }

        // Excel設定のロード
        private static bool LoadExcelSetting(Dictionary<string,string>iniDic, int lineId, out ExcelSettingsDto settingsDto)
        {
            const string INI_KEY_EXCEL_SETTING_FILE_BY_LINE_ID = "ExcelSettingFileByLineId";

            settingsDto = new ExcelSettingsDto();

            var esfbli = DictionaryUxer.GetValueOrDefault(iniDic, INI_KEY_EXCEL_SETTING_FILE_BY_LINE_ID, string.Empty);
            if (string.IsNullOrEmpty(esfbli))
            {
                LoggerUxer.Error($"Excel設定ファイルは未定義です。[キー:{INI_KEY_EXCEL_SETTING_FILE_BY_LINE_ID}]");
                return false;
            }
            var defs = esfbli.Trim().Split('|');
            foreach (var def in defs)
            {
                var items = def.Trim().Split(',');
                if (items.Length != 2)
                {
                    LoggerUxer.Error($"Excel設定ファイルの定義が不正です（カンマ区切り）。[{INI_KEY_EXCEL_SETTING_FILE_BY_LINE_ID}:{esfbli}]");
                    return false;
                }
                if (items[0].Trim() == lineId.ToString())
                {
                    var path = items[1].Trim();
                    if (!path.StartsWith(".\\"))
                    {
                        if (path.StartsWith("\\"))
                        {
                            path = "." + path;
                        }
                        else
                        {
                            path = ".\\" + path;
                        }
                    }
                    if (!File.Exists(path))
                    {
                        LoggerUxer.Error($"Excel設定ファイルが存在しません。[{INI_KEY_EXCEL_SETTING_FILE_BY_LINE_ID}:{esfbli},ラインID:{lineId}]");
                        return false;
                    }

                    using (var book = LoadWorkbookWithoutPhoneticError(path))
                    {
                        if (book.Worksheets.Contains("設定"))
                        {
                            // 設定シートがあった時だけ読む
                            var sheet = book.Worksheet("設定");
                            var tobashiCnt = 0;
                            for (var row = 1; row<1000;row++)
                            {
                                var text = sheet.Cell(row, 1).GetFormattedString().Trim();
                                if (string.IsNullOrEmpty(text) || !text.All(c => c <= 127))
                                {
                                    // A列値が空欄または英数字以外もあるとき、無効行とみなす
                                    tobashiCnt++;
                                    if (tobashiCnt > 10)
                                    {
                                        // おわり
                                        break;
                                    }
                                    continue;
                                }
                                settingsDto.IniList.Add(new IniDto()
                                {
                                    Section = text,
                                    Key = sheet.Cell(row, 2).GetFormattedString().Trim(),
                                    Value = sheet.Cell(row, 3).GetFormattedString().Trim(),
                                });
                            }
                        }

                        // その他のシート
                        foreach (var sheet in book.Worksheets
                            .Where(m => m.Name != "設定"))
                        {
                            var tobashiCnt = 0;
                            Dictionary<string, string> dic = new Dictionary<string, string>();
                            for (var row = 2; row < 1000; row++)
                            {
                                var textA = sheet.Cell(row, 1).GetFormattedString().Trim();
                                var textB = sheet.Cell(row, 2).GetFormattedString().Trim();
                                if (string.IsNullOrEmpty(textA) /*|| !textB.All(c => c <= 127)*/)
                                {
                                    // A列値が空欄またはB列値に英数字以外もあるとき、無効行とみなす
                                    tobashiCnt++;
                                    if (tobashiCnt > 10)
                                    {
                                        // おわり
                                        break;
                                    }
                                    continue;
                                }
                                dic.Add(textA, textB);
                            }
                            settingsDto.KVPDic.Add(sheet.Name, dic);
                        }
                    }
                    break;
                }
            }
            return true;
        }

        /// <summary>
        /// ラインIDを列定義リストに入れ込む
        /// </summary>
        /// <param name="lineId"></param>
        /// <param name="cdList"></param>
        private static void SetLineIdToColumnDifinition(
            int lineId,
            List<ColumnDefinition> cdList)
        {
            foreach (var cd in cdList)
            {
                if (cd.Type == ColumnType.LineId)
                {
                    cd.FixedValue = lineId < 1 ? string.Empty : lineId.ToString();
                }
            }
        }

        /// <summary>
        /// モジュールIDを列定義リストに入れ込む
        /// </summary>
        /// <param name="moduleId"></param>
        /// <param name="cdList"></param>
        private static void SetModuleIdToColumnDifinition(
            int moduleId,
            List<ColumnDefinition> cdList)
        {
            foreach (var cd in cdList)
            {
                if (cd.Type == ColumnType.ModuleId)
                {
                    cd.FixedValue = moduleId < 1 ? string.Empty : moduleId.ToString();
                }
            }
        }

        private static XLWorkbook LoadWorkbookWithoutPhoneticError(string filePath)
        {
            var ms = new MemoryStream();
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                fs.CopyTo(ms);
            }

            ms.Position = 0;
            using (var archive = new ZipArchive(ms, ZipArchiveMode.Update, true))
            {
                var entry = archive.GetEntry("xl/sharedStrings.xml");
                if (entry != null)
                {
                    string content;
                    // 1. まず読み込む（usingで確実にStreamを閉じる）
                    using (var sr = new StreamReader(entry.Open()))
                    {
                        content = sr.ReadToEnd();
                    }

                    // 2. 正規表現でふりがなタグを削除
                    var fixedContent = Regex.Replace(content, @"<rPh[^>]*>.*?</rPh>", "");

                    // 3. 内容を書き換える（Deleteせずに、再度Openして先頭から上書き）
                    // Updateモードなので、Open() して書き込むと上書きされます
                    using (var sw = new StreamWriter(entry.Open(), System.Text.Encoding.UTF8))
                    {
                        // ストリームの長さを0にしてから書き込む（古い内容が残らないように）
                        sw.BaseStream.SetLength(0);
                        sw.Write(fixedContent);
                    }
                }
            }

            ms.Position = 0;
            return new XLWorkbook(ms);
        }
    }
    public class FuncExcelToCsvWeOrderByIniResultDto : IFuncResultDto
    {
        public bool Succeeded { get; set; } = false;
        public int ExitCode { get; set; } = 0;
        public string ResultMessage { get; set; } = string.Empty;
    }

    /// <summary>
    /// 書式つきテキストパターンDTO
    /// </summary>
    public class FormattedTextPatternDto
    {
        public string ColumnName { get; set; } = string.Empty;
        public string JudgeValue { get; set; } = string.Empty;
        public string FileName { get; set; } = string.Empty;
        public string Format { get; set; } = string.Empty;
    }

    /// <summary>
    /// Excel設定
    /// </summary>
    public class ExcelSettingsDto
    {
        public List<IniDto> IniList { get; set; } = new List<IniDto>();
        public Dictionary<string, Dictionary<string, string>> KVPDic { get; set; } = new Dictionary<string, Dictionary<string, string>>();
    }

    public class LineIdHelper
    {
        private const int ID_NOTHING = -1;

        // --- File階層 ---
        private int _fileLineId = ID_NOTHING;
        public int FileLineId
        {
            private get => _fileLineId; // クラス内からだけ読める
            set
            {
                _fileLineId = value;
                FileModuleId = ID_NOTHING;
                SheetLineId = ID_NOTHING;  // 下位を連鎖リセット
            }
        }
        public int FileModuleId { private get; set; } = ID_NOTHING;

        // --- Sheet階層 ---
        private int _sheetLineId = ID_NOTHING;
        public int SheetLineId
        {
            private get => _sheetLineId;
            set
            {
                _sheetLineId = value;
                SheetModuleId = ID_NOTHING;
                RowLineId = ID_NOTHING;    // 下位を連鎖リセット
            }
        }
        public int SheetModuleId { private get; set; } = ID_NOTHING;

        // --- Row階層 ---
        private int _rowLineId = ID_NOTHING;
        public int RowLineId
        {
            private get => _rowLineId;
            set
            {
                _rowLineId = value;
                RowModuleId = ID_NOTHING;  // 最下層なのでここでストップ
            }
        }
        public int RowModuleId { private get; set; } = ID_NOTHING;


        // 🔓 唯一の公開出口：最も深い階層のIDを賢く判定
        public int GetCurrentLineId()
        {
            if (RowLineId != ID_NOTHING) return RowLineId;
            if (SheetLineId != ID_NOTHING) return SheetLineId;
            return FileLineId;
        }

        public int GetCurrentModuleId()
        {
            if (RowLineId != ID_NOTHING) return RowModuleId;
            if (SheetLineId != ID_NOTHING) return SheetModuleId;
            return FileModuleId;
        }
    }

    public class KaniChuzoLpdcJissekiNyuryokuDto
    {
        public DateTime Date { get; set; } = DateTime.MinValue;
        public string Kishu { get; set; } = string.Empty;
        public int Suryo { get; set; } = -1;
        public int Furyo { get; set; } = -1;
        public string Hensu { get; set; } = string.Empty;
    }
    public class KaniChuzoLpdcChuzoInDto
    {
        public string Kishu { get; set; } = string.Empty;
        public string Buban { get; set; } = string.Empty;
        public string Kubun { get; set; } = string.Empty;
        public DateTime Date { get; set; } = DateTime.MinValue;
        public int Su { get; set; } = -1;
    }
    public class KaniChuzoLpdcChuzoInDto
}
