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 PerforPeremployeeRepository _peremployeeRepository;
        private readonly PerforRescomputeRepository _rescomputeRepository;
        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,
            PerforPeremployeeRepository peremployeeRepository,
            PerforRescomputeRepository rescomputeRepository,
            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;
            _peremployeeRepository = peremployeeRepository;
            _rescomputeRepository = rescomputeRepository;
            _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.IsOffice(w.AccountType));

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

            HandsonTableBase handson = GetHandsonTable(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(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(colHeaderCustoms, columnCustoms);
                    break;
                case ComputeMode.Horizontal:
                    handson = ComputeMode_Format2(colHeaderCustoms, columnCustoms);
                    break;
                case ComputeMode.Vertical:
                    handson = ComputeMode_Format3(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 maps = new List<SecondColumnDictionary>()
            {
                new SecondColumnDictionary("人员工号",nameof(ag_bodysource.WorkNumber),true,100),
                new SecondColumnDictionary("姓名",nameof(ag_bodysource.Name),true,100),
                new SecondColumnDictionary("科室",nameof(ag_bodysource.Department),true,100),

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

                new SecondColumnDictionary("工作量绩效合计",nameof(ag_bodysource.WorkPerformance),true,399 ),
                new SecondColumnDictionary("单项奖励合计",nameof(ag_bodysource.DeptReward),true,499 ),

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

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

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

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

                maps.Add(new SecondColumnDictionary("人员系数", nameof(ag_bodysource.StaffCoefficient), false, 200, color: "title_color"));
                maps.Add(new SecondColumnDictionary("出勤", nameof(ag_bodysource.ActualAttendance), false, 201, color: "title_color"));
                maps.Add(new SecondColumnDictionary("职称", nameof(ag_bodysource.JobTitle), false, 202, color: "title_color"));
                maps.Add(new SecondColumnDictionary("职称系数", nameof(ag_bodysource.TitleCoefficient), false, 203, color: "title_color"));
                maps.Add(new SecondColumnDictionary("职称绩效得分", nameof(ag_bodysource.TitlePerformanceScore), false, 204, color: "title_color"));
            }

            // 工作量 
            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_")))
                {
                    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}"));
                    }
                }
                // 多工作量加载 
                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>();
                    foreach (var item in headDynamic.OrderBy(t => t.Id))
                    {
                        maps.Add(new SecondColumnDictionary(item.FieldName, item.FieldId, true, 1, "Top"));
                    }
                }
                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"));
                        maps.Add(new SecondColumnDictionary($"{item.TypeName}金额", $"Workload_Amount_{item.Id}", true, 1, "Top"));
                    }
                }
            }
            // 单项奖励 
            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"));
            }
            return maps.OrderBy(w => w.Sort).ToList();
        }

        /// <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>>();
            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));

            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), (allot.States == (int)AllotStates.GenerateSucceed) ? (second.RealGiveFee ?? 0) : 1000);
            head.AddOrUpdate(nameof(ag_headsource.NightShiftWorkPerforTotal), (allot.States == (int)AllotStates.GenerateSucceed) ? (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", (allot.States == (int)AllotStates.GenerateSucceed) ? "" : "(非正式)");
            // 横向 纵向 特有顶部信息
            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))
                {
                    foreach (var item in headDynamic.Where(w => w.FieldId.StartsWithIgnoreCase("Workload_Ratio_")).OrderBy(t => t.Id))
                    {
                        head.AddOrUpdate(item.FieldId, item.Value ?? 0);
                    }
                }
                // 未提交
                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_"))
                        {
                            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 = 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.To<decimal?>(row.FirstOrDefault(w => w.ItemName == "职称系数")?.ItemValue),
                                StaffCoefficient = ConvertHelper.To<decimal?>(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.小数));
            }

            return (colHeaderCustoms, columnCustoms);
        }
        #endregion

        #region 获取工作量及单项奖励
        /// <summary>
        /// 获取工作量及单项奖励
        /// </summary> 
        /// <param name="hospitalId"></param>
        /// <param name="unitType"></param>
        /// <param name="accountingUnit"></param> 
        /// <returns></returns>
        public List<TitleValue<string, decimal?>> GetWorkLoads(int hospitalId, string unitType, string accountingUnit)
        {
            var workloads = _agworkloadRepository.GetEntities(t => t.HospitalId == hospitalId && t.Department == accountingUnit && t.UnitType == 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(List<string> colHeaders, List<HandsonColumn> columns)
        {
            HandsonTableBase handson = new HandsonTableBase();
            handson.ColHeaders.AddRange(new string[] { "工号", "姓名",/* "核算单元", "领取平均绩效",*/ "职称绩效", "工作量绩效工资", });
            handson.Columns.AddRange(
                new HandsonColumn[]
                {
                    new HandsonColumn(nameof(ag_bodysource.WorkNumber).ToLower()),
                    new HandsonColumn(nameof(ag_bodysource.Name).ToLower()),
                    //new HandsonColumn(nameof(ag_bodysource.Department).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[] { "夜班绩效", });
            handson.Columns.AddRange(new HandsonColumn[] { new HandsonColumn(nameof(ag_bodysource.NightWorkPerformance).ToLower(), format: DataFormat.小数) });

            handson.ColHeaders.AddRange(new string[] { "备注", });
            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(List<string> colHeaders, List<HandsonColumn> columns)
        {
            HandsonTableBase handson = new HandsonTableBase();
            handson.ColHeaders.AddRange(new string[] { "工号", "姓名", /*"核算单元",*/ "领取平均绩效", "人员系数", "出勤", "职称", "职称系数", });
            handson.Columns.AddRange(
                new HandsonColumn[]
                {
                    new HandsonColumn(nameof(ag_bodysource.WorkNumber).ToLower()),
                    new HandsonColumn(nameof(ag_bodysource.Name).ToLower()),
                    //new HandsonColumn(nameof(ag_bodysource.Department).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[] { "夜班绩效", });
            handson.Columns.AddRange(new HandsonColumn[] { new HandsonColumn(nameof(ag_bodysource.NightWorkPerformance).ToLower(), format: DataFormat.小数) });

            handson.ColHeaders.AddRange(new string[] { "备注", });
            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(List<string> colHeaders, List<HandsonColumn> columns, IEnumerable<TitleValue<string, decimal?>> loads)
        {
            HandsonTableBase handson = ComputeMode_Format2(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), 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);
                // 行内可分配绩效
                distPerformanceCalculate(rows);
                // 行内实发绩效
                realAmountCalculate(rows);
            }
            else if (computeMode == ComputeMode.Horizontal || computeMode == ComputeMode.Vertical)
            {
                // 行内计算单项奖励
                deptRewardCalculate(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="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 totalPerformance = GetDecimal2(head, nameof(ag_headsource.TotalDistPerformance)) - GetDecimal2(head, nameof(ag_headsource.NightShiftWorkPerforTotal));
            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 basisPerformance = GetDecimal2(row, nameof(ag_bodysource.BasisPerformance));
                // 可分配绩效 =   当前行职称绩效 + 工作量绩效工资 + 当前行单项奖励 + 当前行主任基础绩效 （科主任护士长）
                var distPerformance = titlePerformance + workPerformance + deptReward + basisPerformance;
                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 total_distPerformance = rows.Sum(row => GetDecimal2(row, nameof(ag_bodysource.DistPerformance)));
            var difference = total_distPerformance - totalPerformance;

            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="second"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        public List<SecondComputeCheckResultDto> CheckData(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(computeMode, loads);
            var fixat = new string[] { nameof(ag_bodysource.Id).ToLower(), nameof(ag_bodysource.SecondId).ToLower(), nameof(ag_bodysource.Department).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), 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.HospitalId, second.UnitType, second.Department);

            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), 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);
        }
    }
}
