/**备品智能化
 * 2023-12-28 Add by dzk
 * 计划外的OPD根据当前日期与拍照日,判断按照上月还是当月排序
 * 获取下月14号——下下月13号,符合条件的OPD计划状态为计划中的备品申请数据
 */
global class OPDAutomaticSortBeforeBatch implements Database.Batchable<sObject> {
    private final List<String> sdcName = null;
    private BatchIF_Log__c iflog;
    private Date specialDate;
    private Boolean allOutofPlan = false;
    // 2024-01-24 ssm 增加一个判断，在月末倒数第二个工作日的时候执行上月排序拷贝操作 start
    private Boolean executeCopyToBefore = false;
    // 2024-01-24 ssm 增加一个判断，在月末倒数第二个工作日的时候执行上月排序拷贝操作 end

    global OPDAutomaticSortBeforeBatch(Boolean exe){
        // 2024-01-24 ssm 增加一个判断，在月末倒数第二个工作日的时候执行上月排序拷贝操作 start
        this.executeCopyToBefore = exe;
        // 2024-01-24 ssm 增加一个判断，在月末倒数第二个工作日的时候执行上月排序拷贝操作 end
        this.iflog = new BatchIF_Log__c();
        this.iflog.Type__c = 'OPDAutomaticSortBeforeBatch';
        this.iflog.Log__c  = 'OPDAutomaticSortBeforeBatch start\n';
        this.iflog.ErrorLog__c = '';
        insert this.iflog;
    }

    global OPDAutomaticSortBeforeBatch(List<String> str){
        this.sdcName = str;

        this.iflog = new BatchIF_Log__c();
        this.iflog.Type__c = 'OPDAutomaticSortBeforeBatch';
        this.iflog.Log__c  = 'OPDAutomaticSortBeforeBatch start\n';
        this.iflog.ErrorLog__c = '';
        insert this.iflog;
    }

    global OPDAutomaticSortBeforeBatch(Date spDate) {
        this.specialDate = spDate;

        this.iflog = new BatchIF_Log__c();
        this.iflog.Type__c = 'OPDAutomaticSortBeforeBatch';
        this.iflog.Log__c  = 'OPDAutomaticSortBeforeBatch start\n';
        this.iflog.ErrorLog__c = '';
        insert this.iflog;
    }

    global OPDAutomaticSortBeforeBatch(Boolean allOP, Date spDate) {
        this.allOutofPlan = allOP;
        this.specialDate = spDate;

        this.iflog = new BatchIF_Log__c();
        this.iflog.Type__c = 'OPDAutomaticSortBeforeBatch';
        this.iflog.Log__c  = 'OPDAutomaticSortBeforeBatch start\n';
        this.iflog.ErrorLog__c = '';
        insert this.iflog;
    }
    
    global Database.QueryLocator start(Database.BatchableContext BC){
        
        String sql = 'SELECT Id,Name,type__c FROM opp2AuxiliarySort__c ';
        sql += 'WHERE (type__c = 1 OR type__c = 11) ';
        if(sdcName <> null){
            sql += 'AND Name IN :sdcName ';
        }
        sql += ' ORDER BY type__c';
        System.debug('=sql='+sql);
        return Database.getQueryLocator(sql);

    }

    global void execute(Database.BatchableContext BC, List<opp2AuxiliarySort__c> opp2AuxiliarySortList) {
        try{
            // 获取当前日期
            Date toDate = this.specialDate != null ? this.specialDate : Date.today();
            // 获取当前月的拍照日
            Date penultimateDate = getPenultimateWorkDayOfMonth(toDate);
            // 判断当前日期与拍照日的关系
            // 今天大于当月拍照日 -> 当月拍照日
            // 今天小于等于当月拍照日 -> 上月拍照日
            penultimateDate = toDate > penultimateDate ? penultimateDate : getPenultimateWorkDayOfMonth(toDate.addMonths(-1));
            // 根据拍照日确认计划实施日范围
            // 获取从次月14日至再次月13日的日期
            Date startDate = penultimateDate == Date.newInstance(2023, 12, 28) ? Date.newInstance(2024,1,1) : penultimateDate.addMonths(1).toStartOfMonth().addDays(13);
            Date endDate = penultimateDate.addMonths(2).toStartOfMonth().addDays(12);
            Map<String,Integer> provinceSortMap = new Map<String,Integer>();
            // 获取符合条件的时间的已排序OPD计划最大排队序号
            // 这里的话要检索自动排序或非自动排序但备品已分配的最大序号
            // 上线后优化
            // 1.增加排序号不为空的判断
            // 2.增加计划外的OPD被取消的判断，状态取消也作为序号最大值的判断条件之一
            // 20240115 ssm 为处理历史数据方便，增加所有计划外手动重新排队分支 start
            String orderquery = 'SELECT Id, Name, OPDLendSort__c FROM OPDPlan__c ';
            orderquery += 'WHERE OPDPlanOCM_man_province_Rental__c = \'' + opp2AuxiliarySortList[0].Name + '\' ';
            orderquery += 'AND OPDPlan_ImplementDate__c >= :startDate ';
            orderquery += 'AND OPDPlan_ImplementDate__c <= :endDate ';
            orderquery += 'AND OPDLendSort__c != null ';
            if (this.allOutofPlan) {
                orderquery += 'AND If_AutoSort__c = 1 ';
            } else {
                // 2024-02-01 ssm 增加非上月残留的条件 start
                // 上月残留的OPD计划就不再重新排序以及占用序号了 
                // orderquery += 'AND (If_AutoSort__c = 1 OR (If_AutoSort__c = 0 AND (Bollowdate__c != null OR Status__c = \'取消\'))) ';
                orderquery += 'AND (If_AutoSort__c = 1 OR (If_AutoSort__c = 0 AND Last_Month_Untreated__c = false AND (Bollowdate__c != null OR Status__c = \'取消\'))) ';
                // 2024-02-01 ssm 增加非上月残留的条件 end 
            }
            orderquery += 'ORDER BY OPDLendSort__c DESC LIMIT 1 ';
            List<OPDPlan__c> beforeOPDPlanList = Database.query(orderquery);
            // List<OPDPlan__c> beforeOPDPlanList = [SELECT Id, Name, OPDLendSort__c
            //                                         FROM OPDPlan__c 
            //                                         WHERE OPDPlanOCM_man_province_Rental__c =: opp2AuxiliarySortList[0].Name
            //                                         AND OPDPlan_ImplementDate__c >=: startDate
            //                                         AND OPDPlan_ImplementDate__c <=: endDate
            //                                         AND OPDLendSort__c != null 
            //                                         AND (If_AutoSort__c = 1 OR (If_AutoSort__c = 0 AND (Bollowdate__c != null OR Status__c = '取消')))
            //                                         ORDER BY OPDLendSort__c DESC LIMIT 1];
            // 20240115 ssm 为处理历史数据方便，增加所有计划外手动重新排队分支 end
            iflog.Log__c += 'last sort: ' + beforeOPDPlanList != null && beforeOPDPlanList.size() > 0 ? beforeOPDPlanList[0].OPDLendSort__c : 0;
            // 获取符合条件的已排序的OPD计划数据
            DateTime penultimateDateTime = DateTime.newInstance(penultimateDate, Time.newInstance(0, 0, 0, 0));
            penultimateDateTime = penultimateDateTime.addHours(8);
            String soql = 'SELECT Id,SortDate__c,OPDPlanOCM_man_province_Rental__c,OPDLendSort__c,OPDLendSortDraft__c,SortOperator__c  FROM OPDPlan__c ';
            //拼接SQL  AccountType__c不一样 两种排序规则
            String soql01 = ' WHERE OPDPlanOCM_man_province_Rental__c = \'' + opp2AuxiliarySortList[0].Name + '\''; 
            // 20240115 ssm 为处理历史数据方便，增加所有计划外手动重新排队分支 start
            if (this.allOutofPlan) {
                soql01 += ' and Status__c != \'草案中\' and Status__c != \'审批中\' ';
            } else {
                soql01 += ' and (Status__c = \'计划中\' OR (Status__c in (\'完毕未报告\', \'待提交报告\', \'完毕\') and OPDLendSort__c = null))';
            }
            // 20240115 ssm 为处理历史数据方便，增加所有计划外手动重新排队分支 end
            // soql01 += ' and OPDPlan_ImplementDate__c >=: startDate ' ;  
            soql01 += ' and OPDPlan_ImplementDate__c <=: endDate ';
            // 2024-02-01 ssm 计划外进入条件调整 start
            // 考虑创建日和审批日不相同的情况，用审批日更合理一些。对象上增加审批通过日，在批准过程最终批准时增加赋值。
            soql01 += ' and (CreatedDate >=: penultimateDateTime ';  
            soql01 += ' or ApprovePassed__c >= :penultimateDate) '; 
            // 2024-02-01 ssm 增加非上月残留的条件 end
            soql01 += ' and AccountType__c = \'学会会议\' ';  
            // 2024-02-01 ssm 增加非上月残留的条件 start
            // 重新排序时也增加相应判断
            soql01 += ' and Last_Month_Untreated__c = false ';
            // 2024-02-01 ssm 增加非上月残留的条件 end 
            soql01 += ' and If_AutoSort__c != 1 ';  
            soql01 += ' order by OPDPlan_ImplementDate__c ASC,CreatedDate ASC ';

            String soql02 = ' WHERE OPDPlanOCM_man_province_Rental__c = \'' + opp2AuxiliarySortList[0].Name + '\''; 
            // 20240115 ssm 为处理历史数据方便，增加所有计划外手动重新排队分支 start
            if (this.allOutofPlan) {
                soql02 += ' and Status__c != \'草案中\' and Status__c != \'审批中\' ';
            } else {
                soql02 += ' and (Status__c = \'计划中\' OR (Status__c in (\'完毕未报告\', \'待提交报告\', \'完毕\') and OPDLendSort__c = null))';
            }
            // 20240115 ssm 为处理历史数据方便，增加所有计划外手动重新排队分支 end
            // soql02 += ' and OPDPlan_ImplementDate__c >=: startDate ' ;  
            soql02 += ' and OPDPlan_ImplementDate__c <=: endDate ';  
            // 2024-02-01 ssm 计划外进入条件调整 start
            // 考虑创建日和审批日不相同的情况，用审批日更合理一些。对象上增加审批通过日，在批准过程最终批准时增加赋值。
            soql02 += ' and (CreatedDate >=: penultimateDateTime ';  
            soql02 += ' or ApprovePassed__c >= :penultimateDate) '; 
            // 2024-02-01 ssm 增加非上月残留的条件 end
            soql02 += ' and AccountType__c = \'医院\' ';  
            // 2024-02-01 ssm 增加非上月残留的条件 start
            // 重新排序时也增加相应判断
            soql02 += ' and Last_Month_Untreated__c = false ';
            // 2024-02-01 ssm 增加非上月残留的条件 end 
            soql02 += ' and If_AutoSort__c != 1 ';  
            // 通过再申请生成的OPD计划 优先度最高，排在最前面
            soql02 += ' order by Reapply__c DESC nulls last, OPDPlan_ImplementDate__c ASC, if_HaveEquipment__c ASC,';
            soql02 += ' if_Newest_HaveOpportunity__c DESC nulls last ,if_OPDTarget__c DESC nulls last,if_HaveSalestarget__c DESC nulls last';
            //分别查询结果后拼接 在遍历排序赋值
            List<OPDPlan__c> tempList01 = Database.query(soql+soql01);
            List<OPDPlan__c> tempList02 = Database.query(soql+soql02);
            tempList01.addAll(tempList02);
            // 省份与排序
            for(OPDPlan__c opdbefore : tempList01){
                if(beforeOPDPlanList == null || beforeOPDPlanList.size() == 0 || beforeOPDPlanList[0].OPDLendSort__c == null){
                    provinceSortMap.put(opdbefore.OPDPlanOCM_man_province_Rental__c, 0);  
                }else{
                    provinceSortMap.put(opdbefore.OPDPlanOCM_man_province_Rental__c, Integer.valueOf(beforeOPDPlanList[0].OPDLendSort__c));  
                }
                
            }

            // 获取符合条件的OPD计划下的出借备品申请   
            List<OPDPlan__c> opdUpdateList = new List<OPDPlan__c>();
            // for(OPDPlan__c opdp : sortedOPDList) {
            for(OPDPlan__c opdp : tempList01) {
                String opdProvince = opdp.OPDPlanOCM_man_province_Rental__c;
                opdp.OPDLendSort__c = provinceSortMap.get(opdProvince) + 1;
                opdp.OPDLendSortDraft__c = provinceSortMap.get(opdProvince) + 1;
                provinceSortMap.put(opdProvince, Integer.valueOf(opdp.OPDLendSort__c));
                opdp.SortDate__c = Date.today();
                opdp.SortOperator__c = userInfo.getUserId();
                opdp.If_AutoSort__c = 0;
                opdUpdateList.add(opdp);
            }

            // if (sortedOPDList != null && sortedOPDList.size() > 0) {
            //     update sortedOPDList;
            // }
            if (opdUpdateList != null && opdUpdateList.size() > 0) {
                update opdUpdateList;
            }

        }catch(Exception ex){
            iflog.ErrorLog__c = ex.getLineNumber()+'  行错误 :   '+ex.getMessage();

            return;
        }
        
    }

    global void finish(Database.BatchableContext BC) {
        iflog.Log__c += '\nOPDAutomaticSortBeforeBatch end' ;
        String tmp = iflog.ErrorLog__c;
        if (tmp != null && tmp.length() > 65000) {
            tmp = tmp.substring(0, 65000);
            tmp += ' ...have more lines...';
            iflog.ErrorLog__c = tmp;
        }
        update iflog;

        // 备品可视化 计划外OPD计算OPD排序No处理
        // 2024-01-24 ssm 增加一个判断，在月末倒数第二个工作日的时候执行上月排序拷贝操作 start
        // 增加一个代参的构造方法，在备品计划外排序完成后更新OPD上的上月残留标记
        // Database.executebatch(new RentalAutomaticSortBeforeBatch(), 1);
        Database.executebatch(new RentalAutomaticSortBeforeBatch(executeCopyToBefore), 1);
        // 2024-01-24 ssm 增加一个判断，在月末倒数第二个工作日的时候执行上月排序拷贝操作 end
    }

    // 获取倒数第二个工作日
    public Date getPenultimateWorkDayOfMonth(Date metaDate) {
        // 获取待查日期+1月的第一天
        Date firstDayOfNextMonth = metaDate.addMonths(1).toStartOfMonth();
        // 此表会维护一个工作日的日历 其中排除掉了周末及节假日 此处通过倒叙排序加limit 2 只拿到倒数第二个工作日
        List<OlympusCalendar__c> olympusCalendarDate = [
                Select Date__c
                From OlympusCalendar__c
                Where Date__c < :firstDayOfNextMonth
                And IsWorkDay__c = 1
                ORDER BY Date__c DESC
                LIMIT 2
        ];
        Date firstWorkDayOfMonth = metaDate;
        if (olympusCalendarDate != null && olympusCalendarDate.size() > 1) {
                firstWorkDayOfMonth = olympusCalendarDate.get(1).Date__c;
        }
        return firstWorkDayOfMonth;
    }
}