using AutoMapper;
using Performance.DtoModels;
using Performance.EntityModels;
using Performance.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Performance.Infrastructure;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Performance.DtoModels.Second;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Drawing;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace Performance.Services
{
    public class RedistributionService : IAutoInjection
    {
        #region 构造函数

        private readonly ILogger<RedistributionService> _logger;
        private readonly SecondAllotDetails _secondAllotDetails;
        private readonly PerforPerallotRepository _perallotRepository;
        private readonly PerforCofaliasRepository _cofaliasRepository;
        private readonly PerforPeremployeeRepository _peremployeeRepository;
        private readonly PerforRescomputeRepository _rescomputeRepository;
        private readonly PerforResaccountRepository _resaccountRepository;
        private readonly PerforAgsecondallotRepository _secondallotRepository;
        private readonly PerforPerapramountRepository _perapramountRepository;
        private readonly PerforAgothersourceRepository _agothersourceRepository;
        private readonly PerforAgfixatitemRepository _agfixatitemRepository;
        private readonly PerforAgheadsourceRepository _agheadsourceRepository;
        private readonly PerforAgbodysourceRepository _agbodysourceRepository;
        private readonly PerforAgworktypesourceRepository _agworktypesourceRepository;
        private readonly PerforAgworkloadRepository _agworkloadRepository;
        private readonly PerforAgworkloadsourceRepository _agworkloadsourceRepository;
        private readonly PerforAgworkloadtypeRepository _agworkloadtypeRepository;
        private readonly PerforImemployeelogisticsRepository _imemployeelogisticsRepository;

        public RedistributionService(
            ILogger<RedistributionService> logger,
            SecondAllotDetails secondAllotDetails,
            PerforPerallotRepository perallotRepository,
            PerforCofaliasRepository cofaliasRepository,
            PerforPeremployeeRepository peremployeeRepository,
            PerforRescomputeRepository rescomputeRepository,
            PerforResaccountRepository resaccountRepository,
            PerforAgsecondallotRepository secondallotRepository,
            PerforPerapramountRepository perapramountRepository,
            PerforAgothersourceRepository agothersourceRepository,
            PerforAgfixatitemRepository agfixatitemRepository,
            PerforAgheadsourceRepository agheadsourceRepository,
            PerforAgbodysourceRepository agbodysourceRepository,
            PerforAgworktypesourceRepository agworktypesourceRepository,
            PerforAgworkloadRepository agworkloadRepository,
            PerforAgworkloadsourceRepository agworkloadsourceRepository,
            PerforAgworkloadtypeRepository agworkloadtypeRepository,
            PerforImemployeelogisticsRepository imemployeelogisticsRepository)
        {
            _logger = logger;
            _secondAllotDetails = secondAllotDetails;
            _perallotRepository = perallotRepository;
            _cofaliasRepository = cofaliasRepository;
            _peremployeeRepository = peremployeeRepository;
            _rescomputeRepository = rescomputeRepository;
            _resaccountRepository = resaccountRepository;
            _secondallotRepository = secondallotRepository;
            _perapramountRepository = perapramountRepository;
            _agothersourceRepository = agothersourceRepository;
            _agfixatitemRepository = agfixatitemRepository;
            _agheadsourceRepository = agheadsourceRepository;
            _agbodysourceRepository = agbodysourceRepository;
            _agworktypesourceRepository = agworktypesourceRepository;
            _agworkloadRepository = agworkloadRepository;
            _agworkloadsourceRepository = agworkloadsourceRepository;
            _agworkloadtypeRepository = agworkloadtypeRepository;
            _imemployeelogisticsRepository = imemployeelogisticsRepository;
        }
        #endregion

        #region 加载
        /// <summary>
        /// 加载
        /// </summary>
        /// <param name="secondId"></param>
        /// <param name="computeMode">计算方式：11 不计算 12 横向计算 13 纵向计算</param>
        /// <param name="overrideMode">数据加载方式：0 保存，1 上次，2 字典，3 测算表</param>
        /// <returns></returns>
        public SecondDetailDto Load(int secondId, ComputeMode computeMode, EmployeeSource overrideMode)
        {
            var second = _secondallotRepository.GetEntity(t => t.Id == secondId);
            if (second == null) throw new PerformanceException("参数SecondId无效！");

            var allot = _perallotRepository.GetEntity(t => t.ID == second.AllotId);
            if (allot == null) throw new PerformanceException("绩效记录不存在！");
            // 填报模板带出 行政工勤 绩效 
            var computes = _rescomputeRepository.GetEntities(w => w.AllotID == allot.ID && UnitTypeUtil.Office.Contains(w.AccountType));

            var loads = GetWorkLoads(allot, second);
            var employees = _peremployeeRepository.GetEntities(w => w.AllotId == second.AllotId);

            HandsonTableBase handson = GetHandsonTable(allot, computeMode, loads);
            // 先占位，更加选择加载指定范围数据
            // 加载方式分 保存，上次，字典，测算表
            var loadEmployees = LoadEmployees(allot, second, employees, overrideMode, out bool isSave);
            // 保存后则不再设置默认值
            if (!isSave)
            {
                // 设置固定信息默认值
                foreach (var item in loadEmployees)
                {
                    item.StaffCoefficient = item.StaffCoefficient ?? 1;  // 人员系数
                    item.ActualAttendance = item.ActualAttendance ?? DateTime.DaysInMonth(allot.Year, allot.Month);  // 出勤
                    item.TitleCoefficient = item.TitleCoefficient ?? 1;  // 职称系数
                    // 填报模板带出 行政工勤 绩效
                    if (computeMode == ComputeMode.NotCalculate)
                        item.WorkPerformance = computes?.Where(w => w.AccountingUnit == second.Department && w.JobNumber?.Trim() == item.WorkNumber?.Trim())?.Sum(w => w.GiveFee) ?? 0;
                }
            }
            //if (overrideMode == EmployeeSource.Workload) isSave = false;
            // 加载已保存工作量数据
            handson.Data = LoadWorkload(allot, second, loadEmployees, isSave, overrideMode);

            // 设置工作量考核等分默认值
            foreach (var item in handson.Data)
            {
                foreach (var score in item.Where(w => w.Key.StartsWithIgnoreCase("AssessmentScore_")).ToList())
                {
                    if (score.Value == null)
                        item.AddOrUpdate(score.Key, 100);
                }
            }

            var head = LoadHead(computeMode, allot, second);
            var dic = GetTableHeaderDictionary(computeMode, allot, second, loads, isAttachFactor: false);
            return new SecondDetailDto { Head = head, Body = handson, Dic = dic };
        }

        /// <summary>
        /// 获取HandonTable内容配置信息
        /// </summary>
        /// <param name="computeMode"></param>
        /// <param name="loads"></param>
        /// <returns></returns>
        public HandsonTableBase GetHandsonTable(per_allot allot, ComputeMode computeMode, List<TitleValue<string, decimal?>> loads)
        {
            var (colHeaderCustoms, columnCustoms) = GetCustomColumns(computeMode, loads);
            HandsonTableBase handson = new HandsonTableBase();
            switch (computeMode)
            {
                case ComputeMode.NotCalculate:
                    handson = ComputeMode_Format1(allot, colHeaderCustoms, columnCustoms);
                    break;
                case ComputeMode.Horizontal:
                    handson = ComputeMode_Format2(allot, colHeaderCustoms, columnCustoms);
                    break;
                case ComputeMode.Vertical:
                    handson = ComputeMode_Format3(allot, colHeaderCustoms, columnCustoms, loads);
                    break;
            }

            return handson;
        }


        /// <summary>
        /// 表格及列头 字典信息
        /// </summary>
        /// <param name="second"></param>
        /// <param name="loads"></param>
        /// <returns></returns>
        public List<SecondColumnDictionary> GetTableHeaderDictionary(ComputeMode computeMode, per_allot allot, ag_secondallot second, List<TitleValue<string, decimal?>> loads, List<SecondWorkLoadDto> workloadGroups = null, bool isAttachFactor = true)
        {
            /*
             此处数据需要额外注意，前端显示规则：通过isTrue=true显示，显示名称为label
             */
            var alias = _cofaliasRepository.GetEntities(t => t.HospitalId == allot.HospitalId && t.Route == "/second_entering" && t.States == 1) ?? new List<cof_alias>();
            Func<string, string, string> getAlias = (name, def) =>
            {
                try
                {
                    var aname = alias?.FirstOrDefault(w => name.Equals(w.Name, StringComparison.OrdinalIgnoreCase))?.Alias ?? "";
                    return string.IsNullOrEmpty(aname) ? def : aname;
                }
                catch { }
                return def;
            };

            var maps = new List<SecondColumnDictionary>()
            {
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.WorkNumber),"人员工号"),nameof(ag_bodysource.WorkNumber),true,100, isNumber: false),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.Name),"姓名"),nameof(ag_bodysource.Name),true,100, isNumber: false),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.Department),"分配科室"),nameof(ag_bodysource.Department),true,100, isNumber: false),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.AccountingUnit),"核算单元"),nameof(ag_bodysource.AccountingUnit),true,100, isNumber: false),

                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.TitlePerformance),"职称绩效"), nameof(ag_bodysource.TitlePerformance), true, 299, isNumber: true),

                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.WorkPerformance),"工作量绩效合计"),nameof(ag_bodysource.WorkPerformance),true,399 ),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.DeptReward),"单项奖励合计"),nameof(ag_bodysource.DeptReward),true,499 ),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.PreDeptReward),"核算平均前单项合计"),nameof(ag_bodysource.PreDeptReward),true,599 ),

                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.DistPerformance),"可分配绩效"),nameof(ag_bodysource.DistPerformance),true,600 ),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.NightWorkPerformance),"医院发放夜班绩效"),nameof(ag_bodysource.NightWorkPerformance),true,602 ),

                //new SecondColumnDictionary("预留比例",nameof(ag_bodysource.ReservedRatio),false,601 ),
                //new SecondColumnDictionary("预留金额",nameof(ag_bodysource.ReservedAmount),false,602 ),

                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.RealAmount),"绩效分配合计"),nameof(ag_bodysource.RealAmount),true,700 , isNumber: true),

                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.OtherPerformance),"医院其他绩效"),nameof(ag_bodysource.OtherPerformance),false,801, color: "referto_color" , isNumber: true),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.ReferToRealAmount),"实发绩效"),nameof(ag_bodysource.ReferToRealAmount),false,802, color: "referto_color" , isNumber: true),
                new SecondColumnDictionary(getAlias(nameof(ag_bodysource.Remark),"备注"),nameof(ag_bodysource.Remark),false,803,width:200, isNumber: false),
            };
            // 填报模板不含一下展示信息
            if (computeMode != ComputeMode.NotCalculate)
            {
                maps.Add(new SecondColumnDictionary(getAlias(nameof(ag_bodysource.Post), "领取平均绩效"), nameof(ag_bodysource.Post), true, 100, isNumber: false));

                maps.Add(new SecondColumnDictionary(getAlias(nameof(ag_bodysource.StaffCoefficient), "人员系数"), nameof(ag_bodysource.StaffCoefficient), false, 200, color: "title_color", isNumber: true));
                maps.Add(new SecondColumnDictionary(getAlias(nameof(ag_bodysource.ActualAttendance), "出勤"), nameof(ag_bodysource.ActualAttendance), false, 201, color: "title_color", isNumber: true));
                maps.Add(new SecondColumnDictionary(getAlias(nameof(ag_bodysource.JobTitle), "职称"), nameof(ag_bodysource.JobTitle), false, 202, color: "title_color", isNumber: false));
                maps.Add(new SecondColumnDictionary(getAlias(nameof(ag_bodysource.TitleCoefficient), "职称系数"), nameof(ag_bodysource.TitleCoefficient), false, 203, color: "title_color", isNumber: true));
                maps.Add(new SecondColumnDictionary(getAlias(nameof(ag_bodysource.TitlePerformanceScore), "职称绩效得分"), nameof(ag_bodysource.TitlePerformanceScore), false, 204, color: "title_color", isNumber: true));
            }
            // 扩展字典
            if (alias != null && alias.Any())
            {
                List<string> notCalculate = new List<string>
                {
                    nameof(ag_bodysource.Post),
                    nameof(ag_bodysource.StaffCoefficient),
                    nameof(ag_bodysource.ActualAttendance),
                    nameof(ag_bodysource.JobTitle),
                    nameof(ag_bodysource.TitleCoefficient),
                    nameof(ag_bodysource.TitlePerformanceScore),
                };
                foreach (var item in alias)
                {
                    if (!maps.Any(w => w.Key.Equals(item.Name, StringComparison.OrdinalIgnoreCase))
                        && !notCalculate.Contains(item.Name))
                    {
                        maps.Add(new SecondColumnDictionary(item.Alias, item.Name, false, item.Sort, color: "title_color", isNumber: false, expand: true));
                    }
                }
            }

            // 工作量 
            if (computeMode != ComputeMode.NotCalculate)
            {
                if (workloadGroups == null)
                    workloadGroups = GetTopWorkloadBodyGroups(loads);

                int workloadSort = 300;
                var index = 0;
                foreach (var item in loads.Where(w => !w.Title.StartsWithIgnoreCase("SingleAwards_") && !w.Title.StartsWithIgnoreCase("PreAccountingReward_")))
                {
                    if (workloadGroups.Any(w => w.Items != null && w.Items.Any(im => im.EqualsIgnoreCase(item.Title))))
                        index = workloadGroups.FindIndex(w => w.Items != null && w.Items.Any(im => im.EqualsIgnoreCase(item.Title)));
                    // 没有明确的工作量项目，则不显示
                    var isExist = workloadGroups
                        .Where(w => w.Items == null || w.Items.Count == 0)
                        .Any(w => item.Title.EqualsIgnoreCase(w.WorkloadScore) || item.Title.EqualsIgnoreCase(w.AssessmentScore) || item.Title.EqualsIgnoreCase(w.WorkPerformance));
                    if (!isExist)
                    {
                        string factor = item.Title.StartsWith("Workload_") ? $"({item.State ?? 0:0.####})" : "";
                        var label = isAttachFactor == true ? $"{item.Value}{factor}" : item.Value;
                        maps.Add(new SecondColumnDictionary(label, item.Title, false, ++workloadSort, type: "Workload", color: $"workload_color{index}", isNumber: true));
                    }
                }
                // 多工作量加载 
                var status = (new int[] { (int)SecondAllotStatus.WaitReview, (int)SecondAllotStatus.PassAudit });
                // 已提交
                if (second.Status.HasValue && status.Contains(second.Status.Value))
                {
                    var headDynamic = _agworktypesourceRepository.GetEntities(t => t.SecondId == second.Id) ?? new List<ag_worktype_source>();
                    var groupDatas = headDynamic
                        .GroupBy(w => new { w.WorkTypeId, w.SecondId, w.FieldId, w.FieldName })
                        .Select(w => w.OrderByDescending(w => w.Id).FirstOrDefault());
                    foreach (var item in groupDatas.OrderBy(t => t.Id))
                    {
                        maps.Add(new SecondColumnDictionary(item.FieldName, item.FieldId, true, 1, "Top", isNumber: true));
                    }
                }
                else
                {
                    var workloadTypes = _agworkloadtypeRepository
                           .GetEntities(t => t.HospitalId == allot.HospitalId && t.Department == second.Department && t.UnitType == second.UnitType) ?? new List<ag_workload_type>();
                    foreach (var item in workloadTypes.OrderBy(t => t.Id))
                    {
                        maps.Add(new SecondColumnDictionary($"{item.TypeName}占比", $"Workload_Ratio_{item.Id}", true, 1, "Top", isNumber: true));
                        maps.Add(new SecondColumnDictionary($"{item.TypeName}金额", $"Workload_Amount_{item.Id}", true, 1, "Top", isNumber: true));
                    }
                }
            }
            // 单项奖励 
            int singleAwardsSort = 400;
            foreach (var item in loads.Where(w => w.Title.StartsWithIgnoreCase("SingleAwards_")))
            {
                maps.Add(new SecondColumnDictionary(item.Value, item.Title, false, ++singleAwardsSort, type: "SingleAwards", color: "singleawards_color", isNumber: true));
            }
            // 核算前奖励 
            int preAccountingRewardSort = 500;
            foreach (var item in loads.Where(w => w.Title.StartsWithIgnoreCase("PreAccountingReward_")))
            {
                maps.Add(new SecondColumnDictionary(item.Value, item.Title, false, ++preAccountingRewardSort, type: "PreAccountingReward", color: "preaccountingreward_color"));
            }
            return maps.OrderBy(w => w.Sort).ToList();
        }

        /// <summary>
        /// 补充内容 扩展字段，通过配置额外展示
        /// </summary>
        /// <param name="allot"></param>
        /// <param name="dic"></param>
        /// <param name="rows"></param>
        public void RowsExpand(per_allot allot, List<SecondColumnDictionary> dic, List<Dictionary<string, object>> rows)
        {
            if (dic != null && dic.Any(w => w.Expand))
            {
                var employees = _perallotRepository.QueryEmployee(allot.ID);
                foreach (var row in rows)
                {
                    var number = row.GetString(nameof(ag_bodysource.WorkNumber));

                    Dictionary<string, object> pairs = new Dictionary<string, object>();

                    var emp = employees.FirstOrDefault(w => w.PersonnelNumber == number);
                    if (emp != null)
                        pairs = JsonHelper.Deserialize<Dictionary<string, object>>(JsonHelper.Serialize(emp));

                    foreach (var item in dic.Where(w => w.Expand))
                    {
                        var value = pairs.GetString(item.Key);
                        row.AddOrUpdate(item.Key, value);
                    }
                }
            }
        }
        /// <summary>
        /// 加载已保存工作量数据，加载时区分：已提交和未提交
        /// </summary>
        /// <param name="allot"></param>
        /// <param name="second"></param>
        /// <param name="loadEmployees"></param>
        /// <param name="isSave">True 加载已保存数据 False 加载工作量自动带出</param>
        /// <returns></returns>
        private List<Dictionary<string, object>> LoadWorkload(per_allot allot, ag_secondallot second, List<ag_bodysource> loadEmployees, bool isSave = true, EmployeeSource overrideMode = 0)
        {
            List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
            if (loadEmployees == null || !loadEmployees.Any()) return result;

            var status = (new int[] { (int)SecondAllotStatus.WaitReview, (int)SecondAllotStatus.PassAudit });
            // 已提交
            if (second.Status.HasValue && status.Contains(second.Status.Value))
            {
                var bodyDynamic = _agworkloadsourceRepository.GetEntities(t => loadEmployees.Select(w => w.Id).Contains(t.BodyId));

                foreach (var employee in loadEmployees)
                {
                    var dict = JsonHelper.Deserialize<Dictionary<string, object>>(JsonHelper.Serialize(employee));
                    if (bodyDynamic != null && bodyDynamic.Any(t => t.BodyId == employee.Id))
                    {
                        foreach (var col in bodyDynamic.Where(t => t.BodyId == employee.Id))
                        {
                            dict.AddOrUpdate(col.ItemId, col.Value);
                        }
                    }
                    result.Add(dict);
                }
            }
            // 未提交
            else
            {
                var bodyDynamic = _agworkloadsourceRepository.GetEntities(t => loadEmployees.Select(w => w.Id).Contains(t.BodyId));
                var workloads = _agworkloadRepository.GetEntities(t => t.HospitalId == allot.HospitalId && t.Department == second.Department && t.UnitType == second.UnitType);

                if (workloads != null && workloads.Any())
                {
                    /* isSave 为True时，没必要查询数据 */
                    var secondWorkload = isSave && overrideMode != EmployeeSource.Workload
                        ? new List<view_second_workload_result>()
                        : GetSecondWorkloadByValue(second.AllotId.Value, second.UnitType, second.Department);

                    foreach (var employee in loadEmployees)
                    {
                        var dict = JsonHelper.Deserialize<Dictionary<string, object>>(JsonHelper.Serialize(employee));
                        foreach (var workitem in workloads)
                        {
                            decimal? value;
                            if (workitem.ItemId.StartsWithIgnoreCase($"SingleAwards_"))
                            {
                                /* 如果是已保存数据，则加载原始数据，否则加载工作量自动带出 */
                                value = isSave
                                   ? bodyDynamic?.FirstOrDefault(w => w.BodyId == employee.Id && w.WorkloadId == workitem.Id)?.Value
                                   : secondWorkload.FirstOrDefault(w => w.PersonnelNumber == employee.WorkNumber && w.DoctorName == employee.Name && workitem.ItemId.EqualsIgnoreCase(w.ItemId))?.Value;
                            }
                            else
                            {
                                /* 如果是已保存数据，则加载原始数据，否则加载工作量自动带出 */
                                value = isSave && overrideMode != EmployeeSource.Workload
                                   ? bodyDynamic?.FirstOrDefault(w => w.BodyId == employee.Id && w.WorkloadId == workitem.Id)?.Value
                                   : secondWorkload.FirstOrDefault(w => w.PersonnelNumber == employee.WorkNumber && w.DoctorName == employee.Name && workitem.ItemId.EqualsIgnoreCase(w.ItemId))?.Value;
                            }
                            dict.AddOrUpdate(workitem.ItemId, value);
                        }
                        result.Add(dict);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 顶部信息加载
        /// </summary>
        /// <param name="computeMode"></param>
        /// <param name="second"></param>
        /// <param name="allot"></param>
        /// <returns></returns>
        private Dictionary<string, object> LoadHead(ComputeMode computeMode, per_allot allot, ag_secondallot second)
        {
            /*
             此处数据需要额外注意，前端显示规则：接口返回则显示
             避免额外返回造成前端错误显示
             */
            var head = new Dictionary<string, object>();
            // 公共顶部信息
            head.AddOrUpdate(nameof(second.Department), second.Department);
            head.AddOrUpdate(nameof(second.UnitType), second.UnitType);
            head.AddOrUpdate(nameof(ComputeMode), computeMode);
            head.AddOrUpdate("ComputeModeDescription", EnumHelper.GetDescription(computeMode));
            var allotStates = new int[] { (int)AllotStates.GenerateSucceed, (int)AllotStates.Archive };
            head.AddOrUpdate(nameof(ag_headsource.SecondId), second.Id);
            head.AddOrUpdate(nameof(ag_headsource.PaymentOfTheMonth), $"{allot.Year}年{allot.Month.ToString().PadLeft(2, '0')}月");
            head.AddOrUpdate(nameof(ag_headsource.TotalDistPerformance), allotStates.Contains(allot.States) ? (second.RealGiveFee ?? 0) : 1000);
            head.AddOrUpdate(nameof(ag_headsource.NightShiftWorkPerforTotal), allotStates.Contains(allot.States) ? (second.NightShiftWorkPerforFee ?? 0) : 0);
            head.AddOrUpdate(nameof(ag_headsource.TotalPerformance), head.GetDecimal(nameof(ag_headsource.TotalDistPerformance)) - head.GetDecimal(nameof(ag_headsource.NightShiftWorkPerforTotal)));
            head.AddOrUpdate("Remark", allotStates.Contains(allot.States) ? "" : "(非正式)");

            #region 添加参考人均 lcr:参考人均那里直接取4.1的人均绩效
            // 添加参考人均（二次分配审核时，提示业务中层测算表的基础绩效或实际人均） 
            var refAvg = 0m;
            if (Enum.TryParse(typeof(UnitType), second.UnitType, true, out object unittype))
            {
                var rescompute = _resaccountRepository.GetEntity(w => w.AccountingUnit == second.Department && w.UnitType == (int)((UnitType)unittype));
                refAvg = rescompute?.Avg ?? 0;
            }
            head.AddOrUpdate("RefAvg", Math.Round(refAvg, 0, MidpointRounding.AwayFromZero));
            #endregion

            // 横向 纵向 特有顶部信息
            if (computeMode != ComputeMode.NotCalculate)
            {
                var history = _agheadsourceRepository.GetEntity(t => t.SecondId == second.Id);

                head.AddOrUpdate(nameof(ag_headsource.SeniorityTitlesAccountedPerformance), history?.SeniorityTitlesAccountedPerformance ?? 0.2m);
                head.AddOrUpdate(nameof(ag_headsource.Workload_Ratio_Default), history?.Workload_Ratio_Default ?? 0.8m);
                head.AddOrUpdate(nameof(ag_headsource.DaysFullAttendance), history?.DaysFullAttendance ?? DateTime.DaysInMonth(allot.Year, allot.Month));

                // 多工作量加载
                var status = (new int[] { (int)SecondAllotStatus.WaitReview, (int)SecondAllotStatus.PassAudit });
                var headDynamic = _agworktypesourceRepository.GetEntities(t => t.SecondId == second.Id) ?? new List<ag_worktype_source>();
                // 已提交
                if (second.Status.HasValue && status.Contains(second.Status.Value))
                {
                    var groupDatas = headDynamic
                        .Where(w => w.FieldId.StartsWithIgnoreCase("Workload_Ratio_"))
                        .GroupBy(w => new { w.WorkTypeId, w.SecondId, w.FieldId, w.FieldName })
                        .Select(w => new { w.Key.FieldId, Value = w.OrderByDescending(w => w.Id).FirstOrDefault()?.Value ?? 0 });

                    foreach (var item in groupDatas)
                    {
                        head.AddOrUpdate(item.FieldId, item.Value);
                    }
                }
                // 未提交
                else
                {
                    List<decimal> ratios = new List<decimal>();
                    var workloadTypes = _agworkloadtypeRepository
                        .GetEntities(t => t.HospitalId == allot.HospitalId && t.Department == second.Department && t.UnitType == second.UnitType) ?? new List<ag_workload_type>();
                    foreach (var workitem in workloadTypes)
                    {
                        var name = $"Workload_Ratio_{workitem.Id}";
                        var ratio = headDynamic.FirstOrDefault(w => w.FieldId.EqualsIgnoreCase(name))?.Value ?? 0m;
                        ratios.Add(ratio);
                        head.AddOrUpdate(name, ratio);
                    }
                    // 强制年资系数及工作量系数合计 等于 1
                    var seniorityTitlesAccountedPerformance = head.GetDecimal(nameof(ag_headsource.SeniorityTitlesAccountedPerformance));
                    var defaultWorkload = 1 - seniorityTitlesAccountedPerformance - ratios.Sum();
                    head.AddOrUpdate(nameof(ag_headsource.Workload_Ratio_Default), defaultWorkload);
                }
            }
            return head;
        }

        /// <summary>
        /// 清理 将所有0赋空
        /// </summary>
        /// <param name="datas"></param>
        public void ClearInvalidValue(List<Dictionary<string, object>> datas)
        {
            if (datas != null)
            {
                foreach (var row in datas)
                {
                    foreach (var key in row.Keys.ToArray())
                    {
                        if (key.StartsWithIgnoreCase("SingleAwards_")
                            || key.StartsWithIgnoreCase("Workload_")
                            || key.StartsWithIgnoreCase("PreAccountingReward_"))
                        {
                            var value = row[key]?.ToString() ?? "";
                            if (value == "0" || Regex.IsMatch(value, "^0.[0]+$"))
                                row[key] = "";
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 按指定方式加载人员数据
        /// </summary>
        /// <param name="allot"></param>
        /// <param name="second"></param>
        /// <param name="mode"></param>
        /// <param name="isSave">true 保存 false 未保存</param>
        /// <returns></returns>
        private List<ag_bodysource> LoadEmployees(per_allot allot, ag_secondallot second, List<per_employee> employees, EmployeeSource mode, out bool isSave)
        {
            isSave = false;
            // 默认流程
            if (new[] { EmployeeSource.Initial, EmployeeSource.Workload }.Contains(mode))
            {
                var saveDatas = _agbodysourceRepository.GetEntities(w => w.SecondId == second.Id);
                isSave = (saveDatas != null && saveDatas.Count > 0);
                // 数据带出顺序 1 已保存 2 上次 3 科室字典(或EXCEL行政工勤)
                if (isSave) return saveDatas;

                var prevSecond = LoadEmployees_PrevSecondAllot(allot, second);
                List<string> numbers = prevSecond?.Where(w => !string.IsNullOrEmpty(w.WorkNumber)).Select(w => w.WorkNumber).ToList() ?? new List<string>();

                // 如果行政工勤科室没有保存数据，则默认从EXCEL中带出数据
                if (UnitTypeUtil.IsOffice(second.UnitType) && (numbers == null || numbers.Count == 0))
                    numbers = LoadEmployees_OfficeExcel(second);

                if (numbers == null || numbers.Count == 0)
                    numbers = LoadEmployees_EmployeeDict(second, employees);

                return LoadEmployeeByDictionary(second, employees, numbers, prevSecond);
            }
            // 用户指定加载
            else
            {
                var prevSecond = LoadEmployees_PrevSecondAllot(allot, second);

                List<string> numbers = new List<string>();
                if (mode == EmployeeSource.PrevSecondAllot)
                    numbers = prevSecond?.Where(w => !string.IsNullOrEmpty(w.WorkNumber)).Select(w => w.WorkNumber).ToList() ?? new List<string>();
                // 如果行政工勤科室则默认从EXCEL中带出数据 
                else if (mode == EmployeeSource.EmployeeDict && UnitTypeUtil.IsOffice(second.UnitType))
                    numbers = LoadEmployees_OfficeExcel(second);
                else if (mode == EmployeeSource.EmployeeDict)
                    numbers = LoadEmployees_EmployeeDict(second, employees);

                if (numbers == null || numbers.Count == 0)
                    return new List<ag_bodysource>();

                return LoadEmployeeByDictionary(second, employees, numbers, prevSecond);
            }
        }

        /// <summary>
        /// 补全信息，带出上次系数
        /// </summary>
        /// <param name="second"></param>
        /// <param name="employees"></param>
        /// <param name="numbers"></param>
        /// <param name="prevSecond"></param>
        /// <returns></returns>
        List<ag_bodysource> LoadEmployeeByDictionary(ag_secondallot second, List<per_employee> employees, List<string> numbers, List<ag_bodysource> prevSecond = null)
        {
            List<ag_bodysource> loadDatas = new List<ag_bodysource>();
            foreach (var personnelNumber in numbers)
            {
                var existEmp = employees?.FirstOrDefault(w => w.PersonnelNumber?.Trim() == personnelNumber?.Trim());

                var data = new ag_bodysource
                {
                    SecondId = second.Id,
                    Department = second.Department ?? "",
                    AccountingUnit = existEmp?.AccountingUnit ?? "",
                    WorkNumber = personnelNumber,
                    Name = existEmp?.DoctorName ?? "花名册无信息",
                    JobTitle = existEmp?.JobTitle ?? "",
                    ReservedRatio = existEmp?.ReservedRatio ?? 1,
                    Post = "",
                };
                // 带出上次 人员系数、职称系数
                if (prevSecond != null)
                {
                    data.StaffCoefficient = prevSecond.FirstOrDefault(w => w.WorkNumber == data.WorkNumber)?.StaffCoefficient ?? data.StaffCoefficient;
                    data.TitleCoefficient = prevSecond.FirstOrDefault(w => w.WorkNumber == data.WorkNumber)?.TitleCoefficient ?? data.TitleCoefficient;
                }

                loadDatas.Add(data);
            }
            return loadDatas;
        }

        /// <summary>
        /// 上次绩效数据
        /// </summary>
        /// <param name="allot"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        private List<ag_bodysource> LoadEmployees_PrevSecondAllot(per_allot allot, ag_secondallot second)
        {
            List<ag_bodysource> numbers = new List<ag_bodysource>();
            // 上次二次分配分三种情况，1.其他来源 2.单工作量 3.多工作量
            var prevSecondAllot = _secondAllotDetails.GetPreviousSecondAllot(allot.HospitalId, second);
            var status = new int[] { (int)SecondAllotStatus.WaitReview, (int)SecondAllotStatus.PassAudit };
            if (prevSecondAllot != null && status.Contains(prevSecondAllot.Status ?? (int)SecondAllotStatus.Uncommitted))
            {
                if (prevSecondAllot.UseTempId == 6)
                {
                    var prevDatas = _agothersourceRepository.GetEntities(w => w.SecondId == prevSecondAllot.Id);
                    numbers = prevDatas
                        ?.Where(pre => !string.IsNullOrEmpty(pre.WorkNumber?.Trim()))
                        .Select(pre => new ag_bodysource { WorkNumber = pre.WorkNumber?.Trim() })
                        .Distinct().ToList() ?? new List<ag_bodysource>();
                }
                else if (prevSecondAllot.UseTempId == 7 || prevSecondAllot.UseTempId == 8)
                {
                    var prevDatas = _agfixatitemRepository
                        .GetEntities(w => w.SecondId == prevSecondAllot.Id && w.RowNumber.HasValue && w.RowNumber > -1 && w.Type == (int)TempColumnType.TableFixedColumns);

                    numbers = prevDatas
                        ?.GroupBy(w => w.RowNumber.Value)
                        .Select(row =>
                            new ag_bodysource
                            {
                                WorkNumber = row.FirstOrDefault(w => w.ItemName == "人员工号")?.ItemValue,
                                TitleCoefficient = ConvertHelper.ToDecimalOrNull(row.FirstOrDefault(w => w.ItemName == "职称系数")?.ItemValue),
                                StaffCoefficient = ConvertHelper.ToDecimalOrNull(row.FirstOrDefault(w => w.ItemName == "人员系数")?.ItemValue),
                            })
                        .Distinct().ToList() ?? new List<ag_bodysource>();

                }
                else
                {
                    var prevDatas = _agbodysourceRepository.GetEntities(w => w.SecondId == prevSecondAllot.Id);
                    numbers = prevDatas
                        ?.Where(pre => !string.IsNullOrEmpty(pre.WorkNumber?.Trim()))
                        .Select(pre =>
                            new ag_bodysource
                            {
                                WorkNumber = pre.WorkNumber,
                                TitleCoefficient = pre.TitleCoefficient,
                                StaffCoefficient = pre.StaffCoefficient
                            })
                        .Distinct().ToList() ?? new List<ag_bodysource>();
                }
            }
            return numbers;
        }

        /// <summary>
        /// 从人员字典带出
        /// </summary>
        /// <param name="second"></param>
        /// <param name="dicEmployees"></param>
        /// <returns></returns>
        private List<string> LoadEmployees_EmployeeDict(ag_secondallot second, List<per_employee> dicEmployees)
        {
            var employees = dicEmployees
                ?.Where(w => w.UnitType == second.UnitType && w.AccountingUnit == second.Department)
                .Select(emp => emp.PersonnelNumber?.Trim())
                .Distinct().ToList() ?? new List<string>();
            return employees;
        }

        /// <summary>
        /// 从测算表带出
        /// </summary>
        /// <param name="second"></param>
        /// <returns></returns>
        private List<string> LoadEmployees_OfficeExcel(ag_secondallot second)
        {
            List<string> numbers = new List<string>();

            var employees = _imemployeelogisticsRepository.GetEntities(w => w.AllotID == second.AllotId && w.AccountingUnit == second.Department);
            if (employees != null)
                numbers = employees.Select(w => w.PersonnelNumber).ToList();

            return numbers;
        }

        #region 动态生成列头信息
        /// <summary>
        /// 动态生成列头信息
        /// </summary>
        /// <param name="computeMode"></param>
        /// <param name="loads"></param>
        /// <returns></returns>
        private (List<string> colHeaderCustoms, List<HandsonColumn> columnCustoms) GetCustomColumns(ComputeMode computeMode, List<TitleValue<string, decimal?>> loads)
        {
            var colHeaderCustoms = new List<string>();
            var columnCustoms = new List<HandsonColumn>();
            // 工作量
            if (computeMode == ComputeMode.Horizontal || computeMode == ComputeMode.Vertical)
            {
                var keys = loads.Where(w => w.Title.StartsWithIgnoreCase("AssessmentScore_")).Select(w => w.Title.Replace("AssessmentScore_", "")).Distinct();
                foreach (var key in keys)
                {
                    if (loads.Any(w => w.Title.StartsWithIgnoreCase($"Workload_{key}_")))
                    {
                        foreach (var item in loads.Where(w => w.Title.EqualsIgnoreCase($"AssessmentScore_{key}") || w.Title.StartsWithIgnoreCase($"Workload_{key}_")))
                        {
                            colHeaderCustoms.Add(item.Value);
                            columnCustoms.Add(new HandsonColumn(item.Title.ToLower(), format: DataFormat.小数));
                        }
                    }
                }
            }
            // 单项奖励 
            foreach (var awards in loads.Where(w => w.Title.StartsWithIgnoreCase("SingleAwards_")))
            {
                colHeaderCustoms.Add(awards.Value);
                columnCustoms.Add(new HandsonColumn(awards.Title.ToLower(), format: DataFormat.小数));
            }
            // 考核前奖励 
            foreach (var reward in loads.Where(w => w.Title.StartsWithIgnoreCase("PreAccountingReward_")))
            {
                colHeaderCustoms.Add(reward.Value);
                columnCustoms.Add(new HandsonColumn(reward.Title.ToLower(), format: DataFormat.小数));
            }

            return (colHeaderCustoms, columnCustoms);
        }
        #endregion

        #region 获取工作量及单项奖励
        /// <summary>
        /// 获取工作量及单项奖励
        /// </summary> 
        /// <param name="allot"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        public List<TitleValue<string, decimal?>> GetWorkLoads(per_allot allot, ag_secondallot second)
        {
            var status = (new int[] { (int)SecondAllotStatus.WaitReview, (int)SecondAllotStatus.PassAudit });
            // 已提交
            if (second.Status.HasValue && status.Contains(second.Status.Value))
            {
                var bodys = _agbodysourceRepository.GetEntities(w => w.SecondId == second.Id);
                var ids = bodys.Select(w => w.Id).ToList();
                var sources = _agworkloadsourceRepository.GetEntities(w => ids.Contains(w.BodyId)) ?? new List<ag_workload_source>();
                var loads = sources
                        .OrderBy(t => t.WorkTypeId).ThenBy(t => t.Sort)
                        .GroupBy(w => new { w.ItemId, w.ItemName })
                        .Select(t => new TitleValue<string, decimal?> { Title = t.Key.ItemId, Value = t.Key.ItemName, State = t.FirstOrDefault()?.FactorValue ?? 0 })
                        .ToList();
                return loads;
            }
            else
            {
                var workloads = _agworkloadRepository.GetEntities(t => t.HospitalId == allot.HospitalId && t.Department == second.Department && t.UnitType == second.UnitType) ?? new List<ag_workload>();
                var loads = workloads
                    .OrderBy(t => t.WorkTypeId).ThenBy(t => t.Sort)
                    .Select(t => new TitleValue<string, decimal?> { Title = t.ItemId, Value = t.ItemName, State = t.FactorValue })
                    .ToList();
                return loads;
            }
        }
        #endregion

        #region 三种计算对应的格式
        /// <summary>
        /// 格式生成，不计算，手动录入
        /// </summary>
        /// <param name="colHeaders"></param>
        /// <param name="columns"></param>
        /// <returns></returns>
        private HandsonTableBase ComputeMode_Format1(per_allot allot, List<string> colHeaders, List<HandsonColumn> columns)
        {
            var alias = _cofaliasRepository.GetEntities(t => t.HospitalId == allot.HospitalId && t.Route == "/second_entering") ?? new List<cof_alias>();
            Func<string, string, string> getAlias = (name, def) =>
            {
                try
                {
                    var aname = alias?.FirstOrDefault(w => name.Equals(w.Name, StringComparison.OrdinalIgnoreCase))?.Alias ?? "";
                    return string.IsNullOrEmpty(aname) ? def : aname;
                }
                catch { }
                return def;
            };

            HandsonTableBase handson = new HandsonTableBase();
            handson.ColHeaders.AddRange(new string[]
            {
                 getAlias(nameof(ag_bodysource.WorkNumber), "工号"),
                 getAlias(nameof(ag_bodysource.Name),"姓名"),
                 getAlias(nameof(ag_bodysource.TitlePerformance), "职称绩效"),
                 getAlias(nameof(ag_bodysource.WorkPerformance),"工作量绩效工资"),
            });

            handson.Columns.AddRange(new HandsonColumn[]
            {
                new HandsonColumn(nameof(ag_bodysource.WorkNumber).ToLower()),
                new HandsonColumn(nameof(ag_bodysource.Name).ToLower()),
                new HandsonColumn(nameof(ag_bodysource.TitlePerformance).ToLower(), format: DataFormat.小数),
                new HandsonColumn(nameof(ag_bodysource.WorkPerformance).ToLower(), format: DataFormat.小数),
            });

            handson.ColHeaders.AddRange(colHeaders);
            handson.Columns.AddRange(columns);

            handson.ColHeaders.AddRange(new string[] { getAlias(nameof(ag_bodysource.NightWorkPerformance), "夜班绩效") });
            handson.Columns.AddRange(new HandsonColumn[] { new HandsonColumn(nameof(ag_bodysource.NightWorkPerformance).ToLower(), format: DataFormat.小数) });

            handson.ColHeaders.AddRange(new string[] { getAlias(nameof(ag_bodysource.Remark), "备注"), });
            handson.Columns.AddRange(new HandsonColumn[] { new HandsonColumn(nameof(ag_bodysource.Remark).ToLower()) });

            return handson;
        }
        /// <summary>
        /// 格式生成，横向计算，不使用系数
        /// </summary>
        /// <param name="colHeaders"></param>
        /// <param name="columns"></param>
        /// <returns></returns>
        private HandsonTableBase ComputeMode_Format2(per_allot allot, List<string> colHeaders, List<HandsonColumn> columns)
        {
            var alias = _cofaliasRepository.GetEntities(t => t.HospitalId == allot.HospitalId && t.Route == "/second_entering") ?? new List<cof_alias>();
            Func<string, string, string> getAlias = (name, def) =>
            {
                try
                {
                    var aname = alias?.FirstOrDefault(w => name.Equals(w.Name, StringComparison.OrdinalIgnoreCase))?.Alias ?? "";
                    return string.IsNullOrEmpty(aname) ? def : aname;
                }
                catch { }
                return def;
            };

            HandsonTableBase handson = new HandsonTableBase();
            handson.ColHeaders.AddRange(new string[]
            {
                getAlias(nameof(ag_bodysource.WorkNumber), "工号"),
                getAlias(nameof(ag_bodysource.Name), "姓名"),
                getAlias(nameof(ag_bodysource.Post), "领取平均绩效"),
                getAlias(nameof(ag_bodysource.StaffCoefficient), "人员系数"),
                getAlias(nameof(ag_bodysource.ActualAttendance), "出勤"),
                getAlias(nameof(ag_bodysource.JobTitle), "职称"),
                getAlias(nameof(ag_bodysource.TitleCoefficient), "职称系数"),
            });

            handson.Columns.AddRange(new HandsonColumn[]
            {
                new HandsonColumn(nameof(ag_bodysource.WorkNumber).ToLower()),
                new HandsonColumn(nameof(ag_bodysource.Name).ToLower()),
                new HandsonColumn(nameof(ag_bodysource.Post).ToLower()){ Type = "autocomplete", Strict = true, Source = new string[] { "是", "否"} },
                new HandsonColumn(nameof(ag_bodysource.StaffCoefficient).ToLower(), format: DataFormat.小数),
                new HandsonColumn(nameof(ag_bodysource.ActualAttendance).ToLower(), format: DataFormat.小数1),
                new HandsonColumn(nameof(ag_bodysource.JobTitle).ToLower()),
                new HandsonColumn(nameof(ag_bodysource.TitleCoefficient).ToLower(), format: DataFormat.小数),
            });

            handson.ColHeaders.AddRange(colHeaders);
            handson.Columns.AddRange(columns);

            handson.ColHeaders.AddRange(new string[] { getAlias(nameof(ag_bodysource.NightWorkPerformance), "夜班绩效"), });
            handson.Columns.AddRange(new HandsonColumn[] { new HandsonColumn(nameof(ag_bodysource.NightWorkPerformance).ToLower(), format: DataFormat.小数) });

            handson.ColHeaders.AddRange(new string[] { getAlias(nameof(ag_bodysource.Remark), "备注"), });
            handson.Columns.AddRange(new HandsonColumn[] { new HandsonColumn(nameof(ag_bodysource.Remark).ToLower()) });
            return handson;
        }

        /// <summary>
        /// 格式生成，纵向计算，使用系数计算
        /// </summary>
        /// <param name="colHeaders"></param>
        /// <param name="columns"></param>
        /// <param name="loads"></param>
        /// <returns></returns>
        private HandsonTableBase ComputeMode_Format3(per_allot allot, List<string> colHeaders, List<HandsonColumn> columns, IEnumerable<TitleValue<string, decimal?>> loads)
        {
            HandsonTableBase handson = ComputeMode_Format2(allot, colHeaders, columns);

            var fs = handson.Columns.Select(col =>
            {
                var f = loads.FirstOrDefault(w => w.Title.StartsWithIgnoreCase($"Workload_") && w.Title.EqualsIgnoreCase(col.Data));
                return f == null ? "" : (f.State ?? 0).ToString("0.####"); ;
            });

            //handson.NestedHeadersArray.Add(handson.ColHeaders);
            //handson.NestedHeadersArray.Add(fs.ToList());

            handson.NestedHeadersArray = new object[] { handson.ColHeaders, fs.ToList() };

            return handson;
        }
        #endregion

        #endregion

        #region 补充医院其他绩效

        /// <summary>
        /// 补充医院其他绩效
        /// </summary>
        /// <param name="second"></param>
        /// <param name="rows"></param>
        public void SupplementOtherPerfor(ag_secondallot second, List<Dictionary<string, object>> rows)
        {
            if (rows == null || rows.Count == 0)
                return;

            var perapramounts = _perapramountRepository
                .GetFullAmount(t => t.AllotId == second.AllotId && t.Status == 3);

            if (perapramounts == null || !perapramounts.Any())
                return;

            foreach (var row in rows)
            {
                var personnelNumber = row.GetString(nameof(ag_bodysource.WorkNumber));
                var amounts = perapramounts.Where(w => UnitTypeUtil.IsEqualsUnitType(w.UnitType, second.UnitType) && w.AccountingUnit == second.Department && w.PersonnelNumber?.Trim() == personnelNumber?.Trim());
                row.AddOrUpdate(nameof(ag_bodysource.OtherPerformance), amounts.Sum(w => w.Amount ?? 0));
            }

            // 补充字典中该科室不存在，但有其它绩效的人员信息
            var groupDatas = perapramounts
                .Where(t => t.UnitType == second.UnitType && t.AccountingUnit == second.Department)
                .GroupBy(w => new { PersonnelNumber = w.PersonnelNumber, DoctorName = w.DoctorName, AccountingUnit = w.AccountingUnit, UnitType = w.UnitType, });
            foreach (var item in groupDatas)
            {
                if (!rows.Any(row => row.GetString(nameof(ag_bodysource.WorkNumber)) == item.Key.PersonnelNumber))
                {
                    rows.Add(new Dictionary<string, object>
                    {
                        { nameof(ag_bodysource.SecondId), second.Id },
                        { nameof(ag_bodysource.WorkNumber), item.Key.PersonnelNumber },
                        { nameof(ag_bodysource.Name), item.Key.DoctorName },
                        { nameof(ag_bodysource.Department), second.Department },
                        { nameof(ag_bodysource.AccountingUnit),  item.Key.AccountingUnit},
                        { nameof(ag_bodysource.OtherPerformance), item.Sum(w => w.Amount )},
                    });
                }
            }
        }
        #endregion

        #region 重算顶部医院其他绩效
        /// <summary>
        /// 计算顶部相关总和>>医院其他绩效(比较特殊，仅用作展示，所以需要重新计算一次)
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        private void otherPerformance(Dictionary<string, object> head, List<Dictionary<string, object>> rows)
        {
            //医院其他绩效总和
            var otherPerformance = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.OtherPerformance)));
            head.AddOrUpdate(nameof(ag_headsource.HosOtherPerformance), otherPerformance);
        }
        #endregion

        #region 计算
        /// <summary>
        /// 分配结果结果计算
        /// </summary>
        /// <param name="computeMode"></param>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="loads"></param>
        /// <param name="workloadGroups"></param>
        public void ResultCompute(ComputeMode computeMode, Dictionary<string, object> head, List<Dictionary<string, object>> rows, List<TitleValue<string, decimal?>> loads, List<SecondWorkLoadDto> workloadGroups)
        {
            var specialPostName = new string[] { "科主任/护士长", "主任", "是", };

            if (computeMode == ComputeMode.NotCalculate)
            {
                // 清空无效数据
                clearPerformanceWorkload(rows, workloadGroups);
                // 行内计算单项奖励
                deptRewardCalculate(rows);
                // 行内计算考核前奖励
                preDeptRewardCalculate(rows);
                // 行内可分配绩效
                distPerformanceCalculate(rows);
                // 行内实发绩效
                realAmountCalculate(rows);
            }
            else if (computeMode == ComputeMode.Horizontal || computeMode == ComputeMode.Vertical)
            {
                // 行内计算单项奖励
                deptRewardCalculate(rows);
                // 行内计算考核前奖励
                preDeptRewardCalculate(rows);
                // 计算顶部相关总和
                overviewCalculate(head, rows);
                // 行内主任基础绩效
                basisPerformanceCalculate(head, rows, specialPostName);
                // 计算顶部相关总和(重算一遍，为了保证减去 主任基础绩效)
                overviewCalculate(head, rows);
                // 计算顶部工作量
                topWorkloadCalculate(head, workloadGroups);
                // 计算顶部年资系数
                topSeniorityCalculate(head);
                // 行内职称绩效计算
                titleCoefficientCalculate(head, rows, specialPostName);
                // 行内工作量分组计算
                workloadCalculate(head, rows, computeMode, loads, workloadGroups, specialPostName);
                // 行内可分配绩效
                distPerformanceCalculate(rows);
                // 差额从主任或第一个人身上扣除
                balanceTotalDistPerformance(head, rows, specialPostName);
                // 行内实发绩效
                realAmountCalculate(rows);
            }
        }

        /// <summary>
        /// 重算顶部医院其他绩效、重算行内实发绩效、重算顶部工作量
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        public void RedistributionCompute(ComputeMode computeMode, Dictionary<string, object> head, List<Dictionary<string, object>> rows)
        {
            // 重算顶部医院其他绩效
            otherPerformance(head, rows);
            // 重算行内实发绩效
            realAmountCalculate(rows);
        }

        /// <summary>
        /// ComputeMode.NotCalculate 不计算时，清空无效数据
        /// </summary>
        /// <param name="rows"></param>
        /// <param name="workloadGroups"></param>
        private void clearPerformanceWorkload(List<Dictionary<string, object>> rows, List<SecondWorkLoadDto> workloadGroups)
        {
            foreach (var row in rows)
            {
                foreach (var workload in workloadGroups)
                {
                    row.AddOrUpdate(workload.WorkloadScore, 0m);
                    row.AddOrUpdate(workload.AssessmentScore, 0m);
                    row.AddOrUpdate(workload.WorkPerformance, 0m);

                    foreach (var item in workload.Items)
                    {
                        row.AddOrUpdate(item, 0m);
                    }
                }
            }
        }

        /// <summary>
        /// 行内计算单项奖励
        /// </summary>
        /// <param name="rows"></param>
        private void deptRewardCalculate(List<Dictionary<string, object>> rows)
        {
            foreach (var row in rows)
            {
                decimal total_deptReward = row.Where(w => w.Key.StartsWithIgnoreCase("SingleAwards_")).Sum(r => GetDecimal(row, r.Key));
                row.AddOrUpdate(nameof(ag_bodysource.DeptReward), GetDecimal2(total_deptReward));
            }
        }

        /// <summary>
        /// 行内计算核算前奖励
        /// </summary>
        /// <param name="rows"></param>
        private void preDeptRewardCalculate(List<Dictionary<string, object>> rows)
        {
            foreach (var row in rows)
            {
                decimal total_deptReward = row.Where(w => w.Key.StartsWithIgnoreCase("PreAccountingReward_")).Sum(r => GetDecimal(row, r.Key));
                row.AddOrUpdate(nameof(ag_bodysource.PreDeptReward), GetDecimal2(total_deptReward));
            }
        }
        /// <summary>
        /// 计算顶部工作量
        /// </summary>
        /// <param name="head"></param>
        /// <param name="workloadGroups"></param>
        private void topWorkloadCalculate(Dictionary<string, object> head, List<SecondWorkLoadDto> workloadGroups)
        {
            foreach (var workload in workloadGroups)
            {
                var amount = GetDecimal2(head.GetDecimal(nameof(ag_headsource.TheTotalAllocationOfPerformanceResults)) * head.GetDecimal($"Workload_Ratio_{workload.Name}"));
                head.AddOrUpdate($"Workload_Amount_{workload.Name}", amount);
            }
        }
        /// <summary>
        /// 计算顶部年资系数
        /// </summary>
        /// <param name="head"></param>
        private void topSeniorityCalculate(Dictionary<string, object> head)
        {
            var amount = GetDecimal2(head.GetDecimal(nameof(ag_headsource.SeniorityTitlesAccountedPerformance)) * head.GetDecimal(nameof(ag_headsource.TheTotalAllocationOfPerformanceResults)));
            head.AddOrUpdate(nameof(ag_headsource.SeniorityTitlesPerformance), amount);
        }

        /// <summary>
        /// 计算顶部相关总和
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        private void overviewCalculate(Dictionary<string, object> head, List<Dictionary<string, object>> rows)
        {
            //// 夜班工作量绩效总和
            //var nightShiftWorkPerforTotal = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.NightWorkPerformance)));
            //head.AddOrUpdate(nameof(ag_headsource.NightShiftWorkPerforTotal), nightShiftWorkPerforTotal);

            // 科室考核前奖励
            var totalPreAccountingReward = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.PreDeptReward)));
            head.AddOrUpdate(nameof(ag_headsource.TotalPreAccountingReward), totalPreAccountingReward);
            // 可分配绩效（顶栏） = 科室总绩效  - 夜班绩效（顶栏） - 科室考核前奖励
            var totalPerformance = GetDecimal2(head, nameof(ag_headsource.TotalDistPerformance)) - GetDecimal2(head, nameof(ag_headsource.NightShiftWorkPerforTotal)) - totalPreAccountingReward;
            head.AddOrUpdate(nameof(ag_headsource.TotalPerformance), totalPerformance);
            //// 医院其他绩效总和
            //var otherPerformance = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.OtherPerformance)));
            //head.AddOrUpdate(nameof(ag_headsource.HosOtherPerformance), otherPerformance);
            //科室核算人数 = 人员系数 * 实际出勤
            var daysFullAttendance = GetDecimal2(head, nameof(ag_headsource.DaysFullAttendance));
            var theNumberOfAccountingDepartment = daysFullAttendance == 0 ? 0m : rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.StaffCoefficient)) * GetDecimal2(row, nameof(ag_bodysource.ActualAttendance)) / daysFullAttendance);
            head.AddOrUpdate(nameof(ag_headsource.TheNumberOfAccountingDepartment), theNumberOfAccountingDepartment);
            // 科室人均 = 可分配绩效 / 科室核算人数
            var departmentsPerCapita = theNumberOfAccountingDepartment == 0 ? 0m : GetDecimal2(totalPerformance / theNumberOfAccountingDepartment);
            head.AddOrUpdate(nameof(ag_headsource.DepartmentsPerCapita), departmentsPerCapita);
            // 科室单项奖励
            var totalDeptReward = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.DeptReward)));
            head.AddOrUpdate(nameof(ag_headsource.TotalDeptReward), totalDeptReward);
            // 主任基础绩效(顶部) =  行内主任基础绩效总和
            var directorBasisPerformance = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.BasisPerformance)));
            head.AddOrUpdate(nameof(ag_headsource.DirectorBasisPerformance), directorBasisPerformance);
            // 业绩分配绩效总额 = 可分配绩效 - 科主任或护士长基础绩效 - 科室单项奖励 - 科室考核前奖励
            var theTotalAllocationOfPerformanceResults = totalPerformance - directorBasisPerformance - totalDeptReward;
            head.AddOrUpdate(nameof(ag_headsource.TheTotalAllocationOfPerformanceResults), theTotalAllocationOfPerformanceResults);
        }

        /// <summary>
        /// 行内主任基础绩效
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="specialPostName"></param>
        private void basisPerformanceCalculate(Dictionary<string, object> head, List<Dictionary<string, object>> rows, string[] specialPostName)
        {
            foreach (var row in rows)
            {
                // 除 科主任/护士长 以外人员没有 主任基础绩效
                // 当前行主任基础绩效  =  科室人均 * 当前行人员系数 * 当前行人员出勤/满勤天数
                var post = row.GetString(nameof(ag_bodysource.Post));
                if (specialPostName.Contains(post))
                {
                    var daysFullAttendance = GetDecimal(head, nameof(ag_headsource.DaysFullAttendance));
                    var departmentsPerCapita = GetDecimal2(head, nameof(ag_headsource.DepartmentsPerCapita));
                    var staffCoefficient = GetDecimal(row, nameof(ag_bodysource.StaffCoefficient));
                    var actualAttendance = GetDecimal(row, nameof(ag_bodysource.ActualAttendance));

                    var basisPerformance = daysFullAttendance == 0 ? 0 : departmentsPerCapita * staffCoefficient * actualAttendance / daysFullAttendance;
                    row.AddOrUpdate(nameof(ag_bodysource.BasisPerformance), basisPerformance);
                }
                else
                {
                    row.AddOrUpdate(nameof(ag_bodysource.BasisPerformance), 0m);
                }
            }
        }

        /// <summary>
        /// 行内职称绩效计算
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="specialPostName"></param>
        private void titleCoefficientCalculate(Dictionary<string, object> head, List<Dictionary<string, object>> rows, string[] specialPostName)
        {
            var total_titleCoefficient = 0m;
            foreach (var row in rows)
            {
                var post = row.GetString(nameof(ag_bodysource.Post));
                if (!specialPostName.Contains(post))
                    total_titleCoefficient += GetDecimal(row, nameof(ag_bodysource.ActualAttendance)) * GetDecimal(row, nameof(ag_bodysource.TitleCoefficient));
            }

            var seniorityTitlesPerformance = GetDecimal(head, nameof(ag_headsource.SeniorityTitlesPerformance));
            foreach (var row in rows)
            {
                var post = row.GetString(nameof(ag_bodysource.Post));
                // 科主任/护士长 不参与职称绩效考核
                if (specialPostName.Contains(post))
                    row.AddOrUpdate(nameof(ag_bodysource.TitleCoefficient), 0m);
                // 职称得分 = 当前行实际出勤 * 当前行职称系数
                var titlePerformanceScore = GetDecimal(row, nameof(ag_bodysource.ActualAttendance)) * GetDecimal(row, nameof(ag_bodysource.TitleCoefficient));
                row.AddOrUpdate(nameof(ag_bodysource.TitlePerformanceScore), titlePerformanceScore);
                //个人职称绩效 =  ( 当前行实际出勤 * 当前行职称系数 ) / 职称系数总和* 年资职称绩效总和
                var titlePerformance = total_titleCoefficient == 0 ? 0 : titlePerformanceScore / total_titleCoefficient * seniorityTitlesPerformance;
                row.AddOrUpdate(nameof(ag_bodysource.TitlePerformance), GetDecimal2(titlePerformance));
            }
        }
        /// <summary>
        /// 行内工作量分组计算
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="computeMode"></param>
        /// <param name="loads"></param>
        /// <param name="workloadGroups"></param>
        /// <param name="specialPostName"></param>
        private void workloadCalculate(Dictionary<string, object> head, List<Dictionary<string, object>> rows, ComputeMode computeMode,
            List<TitleValue<string, decimal?>> loads, List<SecondWorkLoadDto> workloadGroups, string[] specialPostName)
        {
            // 计算方式：1 不计算 2 横向计算 3 纵向计算
            foreach (var row in rows)
            {
                foreach (var gp in workloadGroups)
                {
                    var workload_score = 0m;

                    // 计算方式：1 不计算 2 横向计算 3 纵向计算 
                    switch (computeMode)
                    {
                        case ComputeMode.Horizontal:
                            workload_score = ComputeMode_2(row, gp, specialPostName);
                            break;
                        case ComputeMode.Vertical:
                            workload_score = ComputeMode_3(row, loads, gp, specialPostName);
                            break;
                    }
                    // 工作量得分
                    row.AddOrUpdate(gp.WorkloadScore, workload_score);
                }
            }
            // 工作量每分价格计算
            foreach (var gp in workloadGroups)
            {
                // 汇总行内工作量总得分
                var total_score = rows.Sum(row => GetDecimal(row, gp.WorkloadScore) * GetDecimal(row, gp.AssessmentScore));
                // 计算每分价格
                gp.Unit_Price = total_score == 0 ? 0 : head.GetDecimal($"Workload_Amount_{gp.Name}") / total_score;
            }
            // 行内工作量绩效计算
            foreach (var row in rows)
            {
                var workPerformance = 0m;
                var post = row.GetString(nameof(ag_bodysource.Post));
                foreach (var gp in workloadGroups)
                {
                    // 科主任/护士长 不参与工作量考核
                    if (specialPostName.Contains(post))
                    {
                        row.AddOrUpdate(gp.WorkloadScore, 0m);
                        row.AddOrUpdate(gp.AssessmentScore, 0m);
                    }
                    // 工作量绩效 = 工作量得分 * 每分价格 * 考核得分
                    var workload_fee = row.GetDecimal(gp.WorkloadScore) * gp.Unit_Price * row.GetDecimal(gp.AssessmentScore);
                    row.AddOrUpdate(gp.WorkPerformance, GetDecimal2(workload_fee));
                    workPerformance += workload_fee;
                }
                // 行内工作量汇总到一起
                row.AddOrUpdate(nameof(ag_bodysource.WorkPerformance), GetDecimal2(workPerformance));
            }
        }

        #region 行内工作量 辅助计算
        /// <summary>
        /// 获取系数
        /// </summary>
        /// <param name="workloads"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        decimal getFactorValue(List<TitleValue<string, decimal?>> workloads, string item) => workloads.FirstOrDefault((w) => w.Title == item)?.State ?? 0m;

        /// <summary>
        /// 3 纵向计算 
        /// </summary>
        /// <param name="row"></param>
        /// <param name="loads"></param>
        /// <param name="gp"></param>
        /// <param name="specialPostName"></param>
        /// <returns></returns>
        decimal ComputeMode_3(Dictionary<string, object> row, List<TitleValue<string, decimal?>> loads, SecondWorkLoadDto gp, string[] specialPostName)
        {
            var post = row.GetString(nameof(ag_bodysource.Post));
            var workload_score = gp.Items.Sum((item) =>
            {
                // 科主任/护士长 不参与工作量考核
                if (specialPostName.Contains(post))
                    row.AddOrUpdate(item.ToLower(), 0);
                return row.GetDecimal(item.ToLower()) * getFactorValue(loads, item);
            });
            return workload_score;
        }

        /// <summary>
        /// 2 横向计算
        /// </summary>
        /// <param name="row"></param>
        /// <param name="gp"></param>
        /// <param name="specialPostName"></param>
        /// <returns></returns>
        decimal ComputeMode_2(Dictionary<string, object> row, SecondWorkLoadDto gp, string[] specialPostName)
        {
            var post = row.GetString(nameof(ag_bodysource.Post));
            var workload_score = 0m;
            if (gp.Items.Count % 2 == 0)
            {
                for (int i = 0; i < gp.Items.Count; i = i + 2)
                {
                    // 科主任/护士长 不参与工作量考核
                    if (specialPostName.Contains(post))
                    {
                        row.AddOrUpdate(gp.Items[i], 0m);
                        row.AddOrUpdate(gp.Items[i + 1], 0m);
                    }
                    var amount = row.GetDecimal(gp.Items[i]) * row.GetDecimal(gp.Items[i + 1]);
                    workload_score += amount;
                }
            }
            return workload_score;
        }
        #endregion

        /// <summary>
        /// 行内可分配绩效
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="workloadGroups"></param>
        private void distPerformanceCalculate(List<Dictionary<string, object>> rows)
        {
            foreach (var row in rows)
            {
                var workPerformance = GetDecimal2(row, nameof(ag_bodysource.WorkPerformance));
                var titlePerformance = GetDecimal2(row, nameof(ag_bodysource.TitlePerformance));
                var deptReward = GetDecimal2(row, nameof(ag_bodysource.DeptReward));
                var preDeptReward = GetDecimal2(row, nameof(ag_bodysource.PreDeptReward));
                var basisPerformance = GetDecimal2(row, nameof(ag_bodysource.BasisPerformance));
                // 可分配绩效 =   当前行职称绩效 + 工作量绩效工资 + 当前行单项奖励 + 当前行主任基础绩效 （科主任护士长）
                var distPerformance = titlePerformance + workPerformance + deptReward + basisPerformance + preDeptReward;
                row.AddOrUpdate(nameof(ag_bodysource.DistPerformance), distPerformance);
            }
        }
        /// <summary>
        /// 差额从主任或第一个人身上扣除（小于等于1元）
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="specialPostName"></param>
        private void balanceTotalDistPerformance(Dictionary<string, object> head, List<Dictionary<string, object>> rows, string[] specialPostName)
        {
            var totalPerformance = GetDecimal2(head, nameof(ag_headsource.TotalPerformance));
            var totalPreAccountingReward = GetDecimal2(head, nameof(ag_headsource.TotalPreAccountingReward));
            var total_distPerformance = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.DistPerformance)));
            var difference = total_distPerformance - (totalPerformance + totalPreAccountingReward);

            if (Math.Abs(difference) <= 1)
            {
                var atRow = rows.Where(row => specialPostName.Contains(row.GetString(nameof(ag_bodysource.Post))));
                if (atRow == null || atRow.Count() == 0)
                    atRow = rows;

                if (atRow != null && atRow.Count() > 0)
                {
                    var distPerformance = GetDecimal2(atRow.ElementAt(0), nameof(ag_bodysource.DistPerformance));
                    atRow.ElementAt(0).AddOrUpdate(nameof(ag_bodysource.DistPerformance), distPerformance - difference);
                }
            }
        }
        /// <summary>
        /// 行内实发绩效
        /// </summary>
        /// <param name="head"></param>
        /// <param name="rows"></param>
        /// <param name="specialPostName"></param>
        private void realAmountCalculate(List<Dictionary<string, object>> rows)
        {
            foreach (var row in rows)
            {
                // ReservedAmount 年度考核发放金额     DistPerformance   可分配绩效
                var reservedAmount = GetDecimal2(row, nameof(ag_bodysource.DistPerformance)) * GetDecimal2(row, nameof(ag_bodysource.ReservedRatio));
                row.AddOrUpdate(nameof(ag_bodysource.ReservedAmount), reservedAmount);

                // 实发绩效 = 可分配绩效 - 预留绩效 + 夜班工作量绩效 
                var realAmount = GetDecimal2(row, nameof(ag_bodysource.DistPerformance)) - GetDecimal2(row, nameof(ag_bodysource.ReservedAmount)) + GetDecimal2(row, nameof(ag_bodysource.NightWorkPerformance));
                row.AddOrUpdate(nameof(ag_bodysource.RealAmount), realAmount);

                // 参考(实发绩效 = 实发绩效 + 医院其他绩效)
                var referToRealAmount = realAmount + GetDecimal2(row, nameof(ag_bodysource.OtherPerformance));
                row.AddOrUpdate(nameof(ag_bodysource.ReferToRealAmount), referToRealAmount);
            }
        }

        #region 四射五入 辅助方式
        /// <summary>
        /// 从键值对中获取decimal，默认：0，保留2位小数
        /// </summary>
        /// <param name="pairs"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private decimal GetDecimal2(Dictionary<string, object> pairs, string key) => GetDecimal2(pairs.GetDecimal(key));
        /// <summary>
        /// 从键值对中获取decimal，默认：0，返回原始值
        /// </summary>
        /// <param name="pairs"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private decimal GetDecimal(Dictionary<string, object> pairs, string key) => pairs.GetDecimal(key);

        /// <summary>
        /// decimal?类型转换decimal 默认：0，保留2位小数
        /// </summary>
        /// <param name="pairs"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private decimal GetDecimal2(decimal? value) => value.HasValue ? Math.Round(value.Value, 2, MidpointRounding.AwayFromZero) : 0m;

        #endregion

        #endregion

        #region 获取行内工作量分组数
        /// <summary>
        /// 获取行内工作量分组数
        /// </summary>
        /// <param name="loads"></param>
        /// <returns></returns>
        public List<SecondWorkLoadDto> GetTopWorkloadBodyGroups(List<TitleValue<string, decimal?>> loads)
        {
            List<SecondWorkLoadDto> result = new List<SecondWorkLoadDto>();
            if (loads != null)
            {
                var keys = loads
                    .Where(w => w.Title.StartsWithIgnoreCase("AssessmentScore_"))
                    .Select(w => w.Title.Replace("AssessmentScore_", ""))
                    .Distinct();

                foreach (var key in keys)
                {
                    SecondWorkLoadDto dto = new SecondWorkLoadDto(key);
                    foreach (var item in loads.Where(w => w.Title.StartsWithIgnoreCase($"Workload_{key}_")))
                    {
                        dto.AddItem(item.Title);
                    }
                    result.Add(dto);
                }
            }
            return result;
        }
        #endregion

        #region 检查工号和姓名是否匹配  
        /// <summary>
        /// 二次分配提交数据格式 校验
        /// </summary>
        /// <param name="head"></param>
        /// <param name="body"></param>
        /// <param name="dic"></param>
        /// <returns></returns>
        public List<SecondComputeCheckResultDto> CheckFormat(Dictionary<string, object> head, List<Dictionary<string, object>> body, List<SecondColumnDictionary> dic)
        {
            if (body == null || body.Count == 0)
                throw new PerformanceException("分配人员信息不存在！");

            Regex regex = new Regex(@"^(\-|\+)?\d+(\.\d+)?$");

            List<SecondComputeCheckResultDto> result = new List<SecondComputeCheckResultDto>();

            foreach (var col in dic.Where(w => w.Site == "Top" && w.IsNumber))
            {
                if (head.TryGetValue(col.Key.ToLower(), out object value) && value != null && !string.IsNullOrEmpty(value.ToString()) && !regex.IsMatch(value.ToString()))
                    result.Add(new SecondComputeCheckResultDto(nameof(ResponseType.Error), "", "", $"总览信息：“{col.Label}”错误，当前:“{value}”，仅支持数字，请修复！"));
                else if (head.TryGetValue(col.Key, out value) && value != null && !string.IsNullOrEmpty(value.ToString()) && !regex.IsMatch(value.ToString()))
                    result.Add(new SecondComputeCheckResultDto(nameof(ResponseType.Error), "", "", $"总览信息：“{col.Label}”错误，当前:“{value}”，仅支持数字，请修复！"));
            }

            for (int i = 0; i < body.Count; i++)
            {
                var row = body[i];
                foreach (var col in dic.Where(w => w.Site == "Table" && w.IsNumber))
                {
                    var number = row.GetString(nameof(ag_bodysource.WorkNumber));
                    var name = row.GetString(nameof(ag_bodysource.Name));

                    if (row.TryGetValue(col.Key.ToLower(), out object value) && value != null && !string.IsNullOrEmpty(value.ToString()) && !regex.IsMatch(value.ToString()))
                        result.Add(new SecondComputeCheckResultDto(nameof(ResponseType.Error), number, name, $"第{(i + 1)}行：“{col.Label}”错误，当前:“{value}”，仅支持数字，请修复！"));
                    else if (row.TryGetValue(col.Key, out value) && value != null && !string.IsNullOrEmpty(value.ToString()) && !regex.IsMatch(value.ToString()))
                        result.Add(new SecondComputeCheckResultDto(nameof(ResponseType.Error), number, name, $"第{(i + 1)}行：“{col.Label}”错误，当前:“{value}”，仅支持数字，请修复！"));
                }
            }
            return result;
        }
        /// <summary>
        /// 检查工号和姓名是否匹配
        /// </summary>
        /// <param name="second"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        public List<SecondComputeCheckResultDto> CheckData(per_allot allot, ag_secondallot second, ComputeMode computeMode, List<Dictionary<string, object>> body, List<TitleValue<string, decimal?>> loads)
        {
            if (body == null || body.Count == 0)
                throw new PerformanceException("分配人员信息不存在！");

            List<SecondComputeCheckResultDto> result = new List<SecondComputeCheckResultDto>();

            var employees = _peremployeeRepository.GetEntities(w => w.AllotId == second.AllotId)
                ?.Select(w => new { w.PersonnelNumber, w.DoctorName, w.AccountingUnit, w.UnitType, w.ReservedRatio });

            var handsonTable = GetHandsonTable(allot, computeMode, loads);
            var fixat = new string[] {
                nameof(ag_bodysource.Id).ToLower(),
                nameof(ag_bodysource.SecondId).ToLower(),
                nameof(ag_bodysource.Department).ToLower(),
                nameof(ag_bodysource.AccountingUnit).ToLower()
            };
            for (int i = 0; i < body.Count; i++)
            {
                var item = body[i];
                if (item.Where(w => handsonTable.Columns.Any(col => col.Data.EqualsIgnoreCase(w.Key)) && w.Value != null && !string.IsNullOrEmpty(w.Value.ToString())).Count() > 0)
                {
                    var number = item.GetString(nameof(ag_bodysource.WorkNumber));
                    var name = item.GetString(nameof(ag_bodysource.Name));

                    if (string.IsNullOrEmpty(number) || string.IsNullOrEmpty(name))
                    {
                        item.AddOrUpdate(nameof(ResponseType), ResponseType.Warning);
                        result.Add(new SecondComputeCheckResultDto(nameof(ResponseType.Error), "空", "空", $"第{(i + 1)}行，工号或姓名无效；请删除！"));
                    }
                    else if (employees == null || !employees.Any(w => w.PersonnelNumber == number && w.DoctorName == name))
                    {
                        item.AddOrUpdate(nameof(ResponseType), ResponseType.Error);
                        result.Add(new SecondComputeCheckResultDto(nameof(ResponseType.Error), number, name, $"第{(i + 1)}行，工号和姓名在字典中不存在，请修复！"));
                    }
                    else
                    {
                        var emp = employees?.FirstOrDefault(w => w.PersonnelNumber == number && w.DoctorName == name);
                        item.AddOrUpdate(nameof(ResponseType), ResponseType.OK);
                        item.AddOrUpdate(nameof(ag_bodysource.Department), second.Department ?? "");
                        item.AddOrUpdate(nameof(ag_bodysource.AccountingUnit), emp?.AccountingUnit ?? "");
                        item.AddOrUpdate(nameof(ag_bodysource.ReservedRatio), emp?.ReservedRatio ?? 0);
                    }
                }
            }

            return result;
        }
        #endregion

        #region 二次分配审核后查看详情
        /// <summary>
        /// 二次分配审核后查看详情
        /// </summary>
        /// <param name="computeMode"></param>
        /// <param name="allot"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        public (Dictionary<string, object> head, List<Dictionary<string, object>> rows) RedistributionDetail(ComputeMode computeMode, per_allot allot, ag_secondallot second, List<SecondWorkLoadDto> workloadGroups)
        {
            var loadEmployees = _agbodysourceRepository.GetEntities(w => w.SecondId == second.Id);
            var rows = LoadWorkload(allot, second, loadEmployees);
            var head = LoadHead(computeMode, allot, second);
            if (computeMode != ComputeMode.NotCalculate)
            {
                overviewCalculate(head, rows);
                // 计算顶部工作量
                topWorkloadCalculate(head, workloadGroups);
                // 计算顶部年资系数
                topSeniorityCalculate(head);
            }

            return (head, rows);
        }
        #endregion

        #region 科室查询人员字典
        /// <summary>
        /// 科室查询人员字典
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public object RedistributionEmployee(SecondEmployeeDto request)
        {
            List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();

            var second = _secondallotRepository.GetEntity(t => t.Id == request.SecondId);
            if (second == null) throw new PerformanceException("参数SecondId无效！");

            var allot = _perallotRepository.GetEntity(t => t.ID == second.AllotId);
            if (allot == null)
                throw new PerformanceException("绩效记录不存在！");
            // 当前二次分配配置工作量
            var loads = GetWorkLoads(allot, second);

            Expression<Func<per_employee, bool>> exp = t => t.AllotId == second.AllotId;

            if (request != null && !string.IsNullOrEmpty(request.SearchQuery))
            {
                exp = exp.And(t => true && (t.AccountingUnit.Contains(request.SearchQuery) || t.PersonnelNumber.Contains(request.SearchQuery) || t.DoctorName.Contains(request.SearchQuery) || t.Department.Contains(request.SearchQuery)));
            }
            // 分页查询
            var employees = _peremployeeRepository.GetEntitiesForPaging(request.PageNumber, request.PageSize, exp);
            if (employees != null)
            {
                Func<per_employee, decimal?> getDistPerformance = (emp) => 0;
                //// 不计算模板时，带出工作量绩效（一次分配中的应发绩效）
                //if (request.ComputeMode == (int)ComputeMode.NotCalculate)
                //{
                //    if (UnitTypeUtil.IsOffice(second.UnitType))
                //    {
                //        // 填报模板带出 行政工勤 绩效 
                //        var distPerformance = _rescomputeRepository.GetEntities(t => t.AllotID == second.AllotId && employees.Select(s => s.PersonnelNumber).Contains(t.JobNumber));
                //        getDistPerformance = (emp) =>
                //        {
                //            if (second.Department == emp.AccountingUnit)
                //                return distPerformance?.Where(w => w.JobNumber?.Trim() == emp.PersonnelNumber?.Trim())?.Sum(w => w.GiveFee) ?? 0;
                //            return 0;
                //        };
                //    }
                //}
                var secondWorkload = GetSecondWorkloadByValue(second.AllotId.Value, second.UnitType, second.Department);
                var monthDays = DateTime.DaysInMonth(allot.Year, allot.Month);
                foreach (var employee in employees)
                {
                    Dictionary<string, object> item = new Dictionary<string, object>();

                    //// 不计算模板时，带出工作量绩效（一次分配中的应发绩效）
                    //if (request.ComputeMode == (int)ComputeMode.NotCalculate)
                    //{
                    //    item.Add(nameof(ag_bodysource.WorkPerformance), getDistPerformance(employee));
                    //}

                    item.Add(nameof(per_employee.UnitType), employee.UnitType);
                    item.Add(nameof(ag_bodysource.SecondId), request.SecondId);
                    item.Add(nameof(ag_bodysource.Department), second.Department);
                    item.Add(nameof(ag_bodysource.AccountingUnit), employee.AccountingUnit);
                    item.Add(nameof(ag_bodysource.Name), employee.DoctorName);
                    item.Add(nameof(ag_bodysource.WorkNumber), employee.PersonnelNumber);
                    item.Add(nameof(ag_bodysource.JobTitle), employee.JobTitle);
                    item.Add(nameof(ag_bodysource.Post), "");
                    item.Add(nameof(ag_bodysource.TitleCoefficient), 1);
                    item.Add(nameof(ag_bodysource.ActualAttendance), employee.AttendanceDay > monthDays ? monthDays : employee.AttendanceDay);
                    item.Add(nameof(ag_bodysource.StaffCoefficient), 1);
                    if (loads != null)
                    {
                        foreach (var score in loads.Where(w => w.Title.StartsWithIgnoreCase("AssessmentScore_")).ToList())
                        {
                            item.AddOrUpdate(score.Title, 100);
                        }
                        // 工作量带出
                        if (secondWorkload != null && secondWorkload.Any())
                        {
                            var workload = secondWorkload.Where(w => w.DoctorName == employee.DoctorName && w.PersonnelNumber == employee.PersonnelNumber && loads.Select(score => score.Title).Contains(w.ItemId));
                            foreach (var wl in workload)
                            {
                                item.AddOrUpdate(wl.ItemId, wl.Value);
                            }
                        }
                    }

                    result.Add(item);
                }
            }

            return new { employees.CurrentPage, employees.TotalPages, employees.PageSize, employees.TotalCount, list = result };
        }
        #endregion

        private IEnumerable<view_second_workload_result> GetSecondWorkloadByValue(int allotid, string unittype, string accountingunit)
        {
            return _perallotRepository.GetSecondWorkload(allotid, unittype, accountingunit);
        }

        // 验证数据正确性
        public string ValidationData(ag_secondallot second)
        {
            var datas = _agbodysourceRepository.GetEntities(w => w.SecondId == second.Id);
            var invalid = datas.Where(w => !w.DistPerformance.HasValue);

            string removeEmp = "";
            if (invalid != null && invalid.Count() > 0)
            {
                removeEmp = string.Join(",", invalid.Select(w => w.WorkNumber).ToArray());
                _agbodysourceRepository.RemoveRange(invalid.ToArray());
            }
            return removeEmp;
        }
    }
}
