2 커밋 993057a962 ... 6960f8456e

작성자 SHA1 메시지 날짜
  yuanzhi_kuang 6960f8456e 导出排序 1 주 전
  yuanzhi_kuang 6ac0abd2ae mbos提交 1 개월 전

+ 808 - 0
mbos/mbos_请假单.js

@@ -0,0 +1,808 @@
+_this.pageinit = function () {
+  newPageInit(_this)
+  _this.entryleaveDay={};
+}
+// 	在页面初始化加载之后
+mbos('page').bind('afterLoad', function () {
+  _this.bfTypeValue = "8r0AAAAMhrv9LVKY"; // "8r0AAAA09p9IT3K3"
+  _this.customeBf = "8r0AAAA09p9IT3K3";
+  _this.path = mbos.pageInfo.path// 轻应用的路径编码 例如 trip880
+  _this.detailPageCode = mbos.pageInfo.name.replace('Add', 'View') // 详情页面的编码 (个别页面可特殊处理直接写入) 例如 tripView.editui
+  _this.operateState = mbos.getRequestParams().operateState // 'ADDNEW' 'EDIT' 'VIEW'
+  // 初始化审批流
+  //mbos('nextperson1').checkParticipantPerson({
+  //  "callback": function () { }
+  //});
+  // 单据说明
+  mbos('description').bind('click', function () {
+    handleClickDesc()
+  })
+})
+// 【请假类型】F7过滤, _this.holidayIdList是接口返回的过滤数据,再依据这个过滤F7 
+function filterLeaveTypeF7(index) {
+  var filterStr = _this.holidayIdList.join('\',\'')
+  mbos('entries_sourceHolidayType', index).attr('dynamicFilter', 'id in (\'' + filterStr + '\')'); // 动态添加过滤
+}
+// 哺乳假F7
+function filterBreastTypeF7(index) {  
+  mbos('entries.bfType', index).attr('dynamicFilter', 'state = 1'); // 动态添加过滤
+}
+// 分录渲染后
+mbos('entries').bind('afterRendered', function (e) {
+  _this.leaveLengthTitle = mbos('entries_leaveLength_0').attr('title').split('(')[0] // 获取请假时长的标题
+  _this.bindDateChange(e.index) 
+  _this.getAllDayField(e.index)
+  getHolidayType(e)
+  multiLangField(e.index)// 兼容多语言字段回显的问题
+  //20260507 出差地必填
+   _this.controlPlaceRequired(e.index);
+})
+
+
+
+
+
+//兼容是否有整天请假字段,并根据分录数据的整天请假的值设置时间格式
+_this.getAllDayField = function (index) {
+  var setAllDayTime = function () {
+    if(_this.hasAllDayField === 'noAllDayField'){
+      mbos('entries_isAllDay',index).hide()
+      mbos('entries_isAllDay',index).value(false)
+    }else {
+      mbos('entries_isAllDay',index).show()
+      _this.setTimeVisiable(index)
+    }
+  }
+  if(!_this.hasAllDayField){
+    var callback = function (res) {
+      _this.hasAllDayField = res.data.isAllDay ? 'hasAllDayField' : 'noAllDayField';
+      setAllDayTime()
+    }
+    baseInterface('getDecimalPlace', callback)
+  }else{
+    setAllDayTime()
+  }
+  
+}
+// 保存按钮
+_this.save = function (event) {
+	
+	// ====================== 在这里加开始 20260507 ======================
+  if(!_this.validatePlaceRequired()){
+    return; // 校验不通过,直接拦截,不往下执行
+  }
+  // ====================== 在这里加结束 ======================
+	
+  var params = getModel(-1, 'save') // 封装model,不走common的封装
+  if(params.entries.length > 1){
+    params.billSourceType = '2'
+  }
+  baseInterface('save', toView, -1, params) // 调用保存接口
+}
+// 提交按钮
+_this.submit = function (event) {
+		// ====================== 在这里加开始 20260507 ======================
+  if(!_this.validatePlaceRequired()){
+    return; // 校验不通过,直接拦截,不往下执行
+  }
+  // ====================== 在这里加结束 ======================
+    // 确认提交 弹框
+  mbos.ui.showConfirm({
+    title: localeResource.confirmSubmit,
+    iconclass: "kdfont kdfont-zhuangtai_jingshi the_info",
+    callback: function (data) {
+      mbos('nextperson1').checkParticipantPerson({
+      "editdata":getModel(-1, 'submit'),
+      "callback": function () {
+          if (data == 0) {
+            var params = getModel(-1, 'submit') // 封装model,不走common的封装
+            baseInterface('submit', toView, -1, params) // 调用提交接口
+          }
+        }
+   	  });
+      
+    }
+  });
+
+
+}
+
+// 点击“保存、提交” 跳转页面
+function toView(res) {
+  // 提交接口 返回的res.data为数组,取第一条数据
+  if (res.data.keyValue || (res.data.length > 0 && res.data[0].keyValue)) {
+        // 操作成功 提示弹框
+        mbos.ui.showInfo({
+      title: localeResource.succeed,
+      iconclass: "kdfont kdfont-zhuangtai_wancheng the_success",
+      callback: function () {
+        openPage(_this.path, _this.detailPageCode, { billID: res.data.keyValue || res.data[0].keyValue })
+      }
+    });
+  }
+}
+// 给隐藏的model赋值
+function setModel() {
+  if (typeof _this.operateState != "undefined" && (_this.operateState === "ADDNEW" || _this.operateState === "EDIT")) {
+    var entries = _.cloneDeep(mbos('entity').data.entries) // lodash 深拷贝
+    entries.map(function (entry, i) {
+      entry.person = easContext.person
+//       position和adminOrgUnit取后端默认值,前端取的是人事档案的不是正确的值
+      // entry.position = easContext.position.id
+      // entry.adminOrgUnit = easContext.position.adminOrgUnit.id
+      getEntries(entry, i) // 各单据处理分录数据
+    })
+    return entries
+  }
+}
+//哺乳假 是自定义还是非自定义
+_this.isCustomBr = function(index){
+  var htype = mbos("entries.sourceHolidayType",index).value() ? mbos("entries.sourceHolidayType",index).value().id : ''
+  var bfType = mbos("entries.bfType",index).value() ? mbos("entries.bfType",index).value().id : ''
+  if(htype && bfType && htype===_this.bfTypeValue && bfType!== _this.customeBf){
+    return 'noCustomBf';
+  }else if(htype && bfType && htype===_this.bfTypeValue && bfType === _this.customeBf){
+    return 'isCustomBf';
+  }
+}
+function getModel(index, method) {
+  var entries = setModel()
+  var obj = mbos('entity').data
+  if (method === "save" || method === "submit"){ // 只在保存喝提交时修改枚举字段的存储,不然会清空表头的枚举字段
+    Object.keys(obj).map(function(key){
+      if (obj[key] && obj[key].isenum) {// 遍历枚举字段 依据后端需要 传参只传value
+        obj[key] = obj[key].value
+      }
+    })
+    console.log(obj)    
+  }
+  for(var i=0; i<entries.length;i++){
+    var item = entries[i];
+    var formate = 'YYYY-MM-DD HH:mm:ss'
+    if(item.beginTime){
+      item.beginTime = moment(item.beginTime).format(formate)
+    }
+    if(item.endTime){
+      item.endTime = moment(item.endTime).format(formate)
+    }
+    if (method === "save" || method === "submit"){
+      if(!item.beginYMD){
+        item.beginYMD = item.beginTime ? item.beginTime.split(" ")[0] : '';
+      }
+      if(!item.endYMD){
+        item.endYMD = item.endTime ? item.endTime.split(" ")[0] : '';
+      }
+      if(_this.isCustomBr(i) === 'noCustomBf' && new Date(item.beginTime.split(" ")[0]) <= new Date(item.mLEndTime)){
+        mbos.msgBox.showError(localeResource.prMsg);
+        throw new Error(localeResource.prMsg)
+      }
+      if(item.leaveLength > _this.entryleaveDay[i]){
+        mbos.msgBox.showError(localeResource.detialNo+(i+1)+localeResource.leaveDaysMsg);
+        throw new Error(localeResource.prMsg)
+      }
+    }
+  }
+ // var model = {
+  //  ...obj,
+  //  entries: entries,
+  //}
+  var model =  $.extend({},obj,{entries: entries})
+  if (index >= 0) { // index 为-1 时代表取所有明细
+    model.entries = [entries[index]]
+  }
+  if ((method === "save" || method === "submit") && mbos.getRequestParams().abnormalId) {
+    model.abnormalId = mbos.getRequestParams().abnormalId
+  }
+  var personList = mbos('personSelector1').$element ? mbos('personSelector1').getpersonList() : []; // 抄送人列表
+  model.ccPersonIds = personList.map(function (i) { return (i.personid || i.id) }).join(',')
+  model.ccPerson = personList.map(function (i) { return (i.personid || i.id) }).join(',')
+  return model
+}
+// 各单据处理分录数据的函数
+function getEntries(entry, i) {
+  if (!entry.bfType) entry.bfType = ''
+  var halfData = _this['isHalf' + i]
+  var beginHalf =  mbos('beginHalf', i).value() ?  mbos('beginHalf', i).value().value : ''
+  if (halfData && entry.beginTime && !entry.beginTime.split(' ')[1] && beginHalf) {
+    // 页面显示的是上午下午,但是传参得拼接上时分 格式为年月日时分
+    //entry.beginTime = entry.beginTime + ' ' + (mbos('beginHalf', i).value().value === 'am' ? halfData.amBeginTime : halfData.pmBeginTime)
+    //entry.endTime = entry.endTime + ' ' + (mbos('endHalf', i).value().value === 'am' ? halfData.amEndTime : halfData.pmEndTime)
+    entry.beginTime = entry.beginTime.split(" ")[0] + ' ' + halfData[mbos('beginHalf', i).value().value]
+    entry.endTime = entry.endTime.split(" ")[0] + ' ' + halfData[mbos('endHalf', i).value().value]
+  }
+  // 如果是哺乳假 非自定义的类型 结束时间需要拼接时分为23:59
+  var _holidayType = mbos('entries_sourceHolidayType', i).value()
+  var _bfType = mbos('entries_bfType', i).value()
+  if (_holidayType && _holidayType.name == localeResource.bf_holiday && _bfType && _bfType.name !== localeResource.Customized){
+    entry.beginTime = entry.beginTime.split(" ")[0] + ' 00:00'
+    entry.endTime = entry.endTime.split(" ")[0] + ' 23:59'
+  }
+   if(_holidayType && _holidayType.name !== localeResource.bf_holiday && entry.isAllDay){
+      if(_this['fullBeginTime_'+i]){
+          entry.beginTime = _this['fullBeginTime_'+i];
+          entry.endTime = _this['fullEndTime_'+i];
+      } else {
+          var beginHMS = _this['beginHMS_'+i] ? _this['beginHMS_'+i] : '00:00';
+          var endHMS = _this['endHMS_'+i] ? _this['endHMS_'+i] : '23:59';
+          entry.beginTime = (entry.beginYMD || '') + " " + beginHMS;
+          entry.endTime = (entry.endYMD || '') + " " + endHMS;
+      }
+  }
+}
+// 获取请假类型
+var getHolidayType = _.debounce(function (e) {
+  var callback = function (res) {
+    var list = res.data
+    _this.holidayIdList = res.data.map(function(item){return  item.holidayTypeId.id}) // 存储过滤后的请假类型F7数据
+    filterLeaveTypeF7(e.index)
+    filterBreastTypeF7(e.index)
+    // 请假类型数组
+    _this.holidayType = {}
+    for (var i = 0; i < list.length; i++) {
+      var typeId = list[i].holidayTypeId.id
+      _this.holidayType[typeId] = {
+        holidayPolicyId: list[i].holidayPolicyInfo.id,
+        sourceUnit: list[i].unit,
+        sourceHolidayType: list[i].holidayTypeId,
+        description: list[i].description
+      }
+    }
+    if (mbos.getRequestParams().type && mbos.getRequestParams().holidayTypeId){ 
+      // 从我的假期页面跳转过来,携带赋值假期类型
+      mbos('entries_sourceHolidayType', e.index).value({
+        id:  decodeURIComponent(mbos.getRequestParams().holidayTypeId),
+        name: mbos.getRequestParams().type
+      })
+    }
+    if (mbos('entries_sourceHolidayType', e.index).value()) {
+      _this.typeChange({
+        index: e.index,
+        new_value: mbos('entries_sourceHolidayType', e.index).value(),
+        old_value: ''
+      })
+    }
+  }
+  var date = mbos.getRequestParams().date || new Date()
+  //  需要传参时间,默认为当天 或其他页面带过来的参数
+  var today = moment(date).format('YYYY-MM-DD')
+  if (!mbos('entries_beginTime', e.index).value()) {
+    mbos('entries_beginTime', e.index).value(today + ' 00:00')
+    mbos('entries_beginYMD', e.index).value(today)
+  }
+  if (!mbos('entries_endTime', e.index).value()) {
+    mbos('entries_endTime', e.index).value(today + ' 23:59')
+    mbos('entries_endYMD', e.index).value(today)
+  }
+  setModel()
+  baseInterface('getHolidayTypeByPerson', callback, e.index)
+}, 0)
+// 接口计算请假时长
+var getTimeLength = _.debounce(function (index) {
+  var callback = function (res) {
+    mbos('entries_leaveLength', index).value(res.data.leaveBillDays)
+    _this.entryleaveDay[index] = res.data.leaveBillDays
+  }
+  var addDayCallback = function (res) {
+      var isHalf = _this['isHalf' + index];
+      mbos('entries_leaveLength', index).value(res.data.leaveLength);
+      _this.entryleaveDay[index] = res.data.leaveBillDays
+      if(isHalf){
+        _this['beginHMS_'+index] = res.data.amStartWorkTime
+        _this['endHMS_'+index] = res.data.pmEndWorkTime
+      }else{
+        _this['beginHMS_'+index] = res.data.beginHMS
+        _this['endHMS_'+index] = res.data.endHMS
+      }
+
+      var beginHMS = isHalf ? res.data.amStartWorkTime : res.data.beginHMS;
+      var endHMS = isHalf ? res.data.pmEndWorkTime : res.data.endHMS;
+      _this['fullBeginTime_'+index] = res.data.beginYMD.split(" ")[0] + " " + beginHMS;
+      _this['fullEndTime_'+index] = res.data.endYMD.split(" ")[0] + " " + endHMS;
+
+      _this.changeTime = true
+      mbos("entries_beginTime_"+index).value(res.data.beginYMD);
+      mbos("entries_endTime_"+index).value(res.data.endYMD);
+      setTimeout(function(){
+        _this.changeTime = false
+        $("#entries_beginTime_"+index+" .ng-pristine").val(_this['fullBeginTime_'+index])
+        $("#entries_endTime_"+index+" .ng-pristine").val(_this['fullEndTime_'+index])
+      })
+  }
+  var params = getModel(index,'changeLeaveBillTime') // 封装model,不走common的封装
+  var entry = mbos('entity').data.entries[index]
+  if (entry.beginTime && entry.endTime && entry.sourceHolidayType) {
+     //整天请假
+     var  holidayTypeId = entry.sourceHolidayType.id;
+     if(entry.isAllDay && holidayTypeId !== _this.bfTypeValue){
+       var param = _.cloneDeep(params);
+       param.entries[0].beginTime =entry.beginYMD ||entry.beginTime.split(" ")[0];
+       param.entries[0].endTime =  entry.endYMD || entry.endTime.split(" ")[0];
+       param.entries[0].holidayTypeId = holidayTypeId
+       param.entries[0].personId = params.entries[0].person.id;
+       baseInterface('changeLeaveBillTime',addDayCallback, index, param)
+     //非整体请假
+     }
+    baseInterface('getBillLength', callback, index, params)
+  }
+}, 500)
+// 获取假期剩余额度单位
+var getRemainLength = _.debounce(function (event) {
+  var index = event.index
+  var entry = mbos('entity').data.entries[index]
+  delete entry.SourceHolidayType
+  if (entry.beginTime && entry.endTime && entry.sourceHolidayType) {
+    var callback = function (res) {
+      if (!res.data) {
+        $('#remainCard_' + index).css('display', 'none')
+        return
+      }
+      if (res && res.data) {
+        var type = _this.holidayType[mbos('entries_sourceHolidayType', index).value().id]
+        $('#remainCard_' + index).css('display', 'flex')
+        //document.getElementById('remainCard_' + index).innerText = `${type.sourceHolidayType.name}  ${localeResource.remained}${res.data.remainLimitVal}${type.sourceUnit.alias} ${localeResource.remainedDays}`
+     	document.getElementById('remainCard_' + index).innerText = type.sourceHolidayType.name+' '+localeResource.remained+res.data.remainLimitVal+type.sourceUnit.alias+localeResource.remainedDays
+
+      }
+    }
+    setModel()
+    baseInterface('getHolidayLimitByHolidayType', callback, index)
+  }
+}, 200)
+// 根据时间判断 是否展示弹性算时长字段
+var isElastic = _.debounce(function (index) {
+  var callback = function (res) {
+    if (res.data) {
+      mbos('entries_isElasticCalLen', index).show()
+    } else {
+      mbos('entries_isElasticCalLen', index).hide()
+    }
+  }
+  //   TODO   holidayPolicyId待添加
+  setModel(index)
+  var entry = mbos('entity').data.entries[index]
+  if (entry.beginTime && entry.endTime) {
+    baseInterface('showIsElasticCalCtrl', callback, index)
+  }
+}, 500)
+
+/**
+ * 修改开始结束时间的格式
+ * YYYY-MM-DD      : 非【自定义】的所有哺乳假类型
+ * YYYY-MM-DD HH:mm:未启用半天假的非哺乳假;【自定义】的哺乳假类型且未启用半天假;
+ * YYYY-MM-DD AM/PM:启用半天假的非哺乳假、【自定义】的哺乳假类型且启用半天假 
+ */
+function beginEndFormat(format, index) {
+  mbos('entries.beginTime', index).timeFormat(format);
+  mbos('entries.endTime', index).timeFormat(format);
+}
+
+// 是否开启半天假
+function isHalfHoliday(index, timeFormat) {
+  var callback = function (res) {
+    if (res.data && res.data.isHalfDayOff) {
+      //       先赋值 在设置半天假 最后格式化时间,顺序不能变
+      _this['isHalf' + index] = res.data
+      // setHalfDay(index)// 设置开启半天假的样式
+      // beginEndFormat('YYYY-MM-DD', index)
+    } else {
+      _this['isHalf' + index] = false
+      // hideHalfDay(index)// 隐藏半天假
+      // beginEndFormat(timeFormat ? timeFormat : 'YYYY-MM-DD HH:mm', index)
+    }
+    _this.setTimeVisiable(index);
+  }
+  setModel()
+  baseInterface('getSetIsCtrlHalfDayOff', callback, index)
+}
+// 设置开启半天假的样式
+function setHalfDay1(index) {
+  $('#entries_beginTime_' + index).removeClass('col-xs-12').addClass('col-xs-9')
+  $('#entries_endTime_' + index).removeClass('col-xs-12').addClass('col-xs-9')
+  // mbos('beginHalf', index).show()
+  // mbos('endHalf', index).show()
+  $('#beginHalf_' + index).css("display", "block")
+  $('#endHalf_' + index).css("display", "block")
+  var half = {
+    am: { alias: localeResource.AM, value: "am" },
+    pm: { alias: localeResource.PM, value: "pm" },
+  }
+  var halfData = _this['isHalf' + index] // 半天假及时间字段
+  // 编辑时 根据时间判断
+  var startHM = halfData.pmBeginTime === mbos('entries_beginTime', index).value().split(' ')[1] ? half.pm : half.am
+  var endHM = halfData.amEndTime === mbos('entries_endTime', index).value().split(' ')[1] ? half.am : half.pm
+  //mbos('beginHalf', index).value(startHM)
+  //mbos('endHalf', index).value(endHM)
+  if(!mbos('beginHalf', index).value()){
+      mbos('beginHalf', index).value(startHM)
+    }
+    if(!mbos('endHalf', index).value()){
+      mbos('endHalf', index).value(endHM)
+    }
+}
+// 设置开启半天假的样式
+function setHalfDay(index) {
+  // 样式变动
+  $('#entries_beginTime_' + index).removeClass('col-xs-12').addClass('col-xs-8')
+  $('#entries_endTime_' + index).removeClass('col-xs-12').addClass('col-xs-8')
+  $('#beginHalf_' + index).css("display", "block")
+  $('#endHalf_' + index).css("display", "block")
+  halfClick(index) // 半天枚举控件的点击事件
+  var halfData = _this['isHalf' + index] // 半天假及时间字段
+  var half = {}
+  half[halfData.amBeginTime] = { alias: localeResource.AM + ' ' + halfData.amBeginTime, value: "amBeginTime" }
+  half[halfData.amEndTime] = { alias: localeResource.AM + ' ' + halfData.amEndTime, value: "amEndTime" }
+  half[halfData.pmBeginTime] = { alias: localeResource.PM + ' ' + halfData.pmBeginTime, value: "pmBeginTime" }
+  half[halfData.pmEndTime] = { alias: localeResource.PM + ' ' + halfData.pmEndTime, value: "pmEndTime" }
+  _this.halfEnum = half
+  // 编辑时 根据时间判断
+  var sTime = mbos('entries_beginTime', index).value().split(' ')[1]
+  var eTime = mbos('entries_endTime', index).value().split(' ')[1]
+  var startHM = half[sTime] || half[halfData.amBeginTime]
+  var endHM = half[eTime] || half[halfData.pmEndTime]
+  
+  mbos('beginHalf', index).value(startHM)
+  mbos('endHalf', index).value(endHM)
+}
+// 半天控件的点击事件 给枚举选项拼接时分
+function halfClick(index){
+  var list = ["amBeginTime","amEndTime","pmBeginTime","pmEndTime"]
+  $('#beginHalf_'+index).click(function () {
+    setTimeout(function(){
+      var enumList = $('.seldiv .ng-binding')
+      for (var i = 0; i < enumList.length; i++) {
+        if(i == 1 || i == 3){
+          enumList.eq(i).closest('li').hide();
+        }
+        enumList[i].innerHTML = enumList[i].innerHTML.split(' ')[0] + ' ' + _this['isHalf' + index][list[i]]
+      }
+    }, 0)
+  })
+  $('#endHalf_'+index).click(function () {
+    setTimeout(function(){
+      var enumList = $('.seldiv .ng-binding')
+      for (var i = 0; i < enumList.length; i++) {
+        enumList.eq(i).closest('li').show();
+        if(i == 0 || i == 2){
+          enumList.eq(i).closest('li').hide();
+        }
+        enumList[i].innerHTML = enumList[i].innerHTML.split(' ')[0] + ' ' + _this['isHalf' + index][list[i]]
+      }
+    }, 0)
+  })
+}
+// 隐藏半天假
+function hideHalfDay(index) {
+  $('#entries_beginTime_' + index).removeClass('col-xs-8').addClass('col-xs-12')
+  $('#entries_endTime_' + index).removeClass('col-xs-8').addClass('col-xs-12')
+  mbos('beginHalf', index).hide()
+  mbos('endHalf', index).hide()
+}
+// 监听请假类型
+_this.typeChange = function (event) {
+  var index = event.index
+  var entry = mbos('entity').data.entries[index]
+  if (!event.new_value) return //清空
+  var type = _this.holidayType && _this.holidayType[event.new_value.id]
+  if (type) {
+    entry.sourceUnit = type.sourceUnit // 1是天 2是小时
+    entry.holidayPolicyId = type.holidayPolicyId
+    entry.SourceHolidayType = type.sourceHolidayType.id
+  }
+  // 是否是哺乳假
+  var sourceHolidayTypeValue = mbos('entries_sourceHolidayType', index).value();
+  if (sourceHolidayTypeValue && (sourceHolidayTypeValue.id ==  _this.bfTypeValue || sourceHolidayTypeValue.name == localeResource.bf_holiday)) {
+    mbos('entries.isAllDay', index).hide()
+    mbos('entries_isAllDay',index).value(false)
+    // 请假类型为【哺乳假】则显示 哺乳假类型、子女出生日期、产假结束日期
+    mbos('entries.bfType', index).show()
+    var format = 'YYYY-MM-DD'
+    if (mbos('entries_bfType', index).value()) {
+      if(mbos('entries_bfType', index).value().name == localeResource.Customized){
+        format = 'YYYY-MM-DD HH:mm'
+      } else {
+        mbos('entries.childbirthday', index).show()
+        mbos('entries.mLEndTime', index).show()
+      }
+    } else {
+	  // 如果哺乳假类型不存在则默认为自定义
+      mbos('entries_bfType', index).value({id: _this.customeBf, name: localeResource.Customized})
+      format = 'YYYY-MM-DD HH:mm'
+    }
+    
+    isHalfHoliday(index, format) // 判断是否启用半天假
+  } else {// 非哺乳假时间格式只有两种:【年月日时分】、【年月日 上/下午】。启用半天假时为【年月日 上/下午】,其他的都是【年月日时分】;
+    // 其他类型则隐藏
+    mbos('entries.bfType', index).hide()
+    mbos('entries_bfType', index).value('')
+    mbos('entries.childbirthday', index).hide()
+    mbos('entries.childbirthday', index).value('')
+    mbos('entries.mLEndTime', index).hide()
+    mbos('entries.mLEndTime', index).value('')
+    if(_this.hasAllDayField === 'hasAllDayField'){
+      mbos('entries.isAllDay', index).show()
+    }
+   
+    isHalfHoliday(index) // 判断是否启用半天假
+  }
+  if (entry.sourceUnit) {
+     mbos('entries_leaveLength', index).attr('title', _this.leaveLengthTitle +`(${entry.sourceUnit.alias})`);
+  }
+  getTimeLength(index)
+  getRemainLength(event)
+  getExplain(index)
+}
+// 监听 开始时间
+_this.calTime1 = function (event) {
+  if(_this.changeTime) return;
+  var beginalue = mbos('entries.beginTime',event.index).value();
+  var endValue = mbos('entries.endTime',event.index).value();
+  var newEndValue = endValue;
+  if(new Date(beginalue).getTime() > new Date(endValue).getTime()){
+    if(endValue && endValue.split(' ').length >= 2){
+      newEndValue = beginalue.split(' ')[0] + ' ' + endValue.split(' ')[1];
+      mbos('entries.endTime',event.index).value(newEndValue.split(' ')[0]+" 23:59");
+    }else{
+      newEndValue = beginalue;
+       mbos('entries.endTime',event.index).value(newEndValue);
+    }
+   
+    mbos("entries.endYMD",event.index).value(newEndValue);
+  }
+  getHolidayType(event)
+  getTimeLength(event.index)
+  isElastic(event.index)
+  getRemainLength(event)
+}
+// 监听 结束时间
+_this.calTime2 = function (event) {
+  if(_this.changeTime) return;
+  var beginalue = mbos('entries.beginTime',event.index).value();
+  var endValue = mbos('entries.endTime',event.index).value();
+  var newBeginValue = beginalue;
+  if(new Date(beginalue).getTime() > new Date(endValue).getTime()){
+    if(beginalue && beginalue.split(' ').length >= 2){
+      newBeginValue = endValue.split(' ')[0] + ' ' + endValue.split(' ')[1];
+      mbos('entries.beginTime',event.index).value(newBeginValue.split(' ')[0]+" 00:00");
+    }else{
+      newBeginValue = endValue;
+      mbos('entries.beginTime',event.index).value(newBeginValue);
+    }
+   
+    mbos("entries.beginYMD",event.index).value(newBeginValue);
+  }
+  getHolidayType(event)
+  getTimeLength(event.index)
+  isElastic(event.index)
+  getRemainLength(event)
+}
+// 监听 子女出生日期
+_this.birthdayChange = function (event) {
+  // 请假开始时间默认值:产假结束日期,再加1天;
+  if (event.new_value) {
+    var value = moment(mbos('entries_childbirthday', event.index).value()).add(1, 'years').format('YYYY-MM-DD')
+    value = moment(value).subtract(1, 'days').format('YYYY-MM-DD')
+    mbos('entries.endTime', event.index).value(value)
+  }
+}
+// 监听 产假结束日期
+_this.mlEndChange = function (event) {
+  // 请假结束时间默认值:子女出生日期延后一年,再减1天;
+  if (event.new_value) {
+    var value = moment(mbos('entries_mLEndTime', event.index).value()).add(1, 'days').format('YYYY-MM-DD')
+    mbos('entries.beginTime', event.index).value(value)
+  }
+}
+// 监听 哺乳假类型
+_this.bfTypeChange = function (event) {
+  var index = event.index
+  // 哺乳假类型是否为【自定义】
+  if (!mbos('entries_bfType', index).value()){
+     // 如果哺乳假类型不存在则默认为自定义
+      setTimeout(function(){
+        mbos('entries_bfType', index).value({id: _this.customeBf, name: localeResource.Customized})
+      }, 0)
+  }
+  if (mbos('entries_bfType', index).value() && (mbos('entries_bfType', index).value().id == _this.customeBf || mbos('entries_bfType', index).value().name == localeResource.Customized)) {
+    //   【自定义】: 则不展示【子女出生日期、产假结束日期】
+    mbos('entries.childbirthday', index).value('')
+    mbos('entries.childbirthday', index).hide()
+    mbos('entries.mLEndTime', index).value('')
+    mbos('entries.mLEndTime', index).hide()
+    // 哺乳假类型为【自定义】时,启用半天假时为【年月日 上/下午】,其他的都是【年月日 时分】
+    isHalfHoliday(index) // 判断是否启用半天假
+  } else {
+    // 非【自定义】的所有哺乳假类型 日期格式为年月日
+    beginEndFormat('YYYY-MM-DD', index)
+    mbos('entries.childbirthday', index).show()
+    mbos('entries.mLEndTime', index).show()
+  }
+  getTimeLength(event.index)
+}
+// 监听是否是弹性算时长
+_this.isElasticChange = function (event) {
+  getTimeLength(event.index)
+}
+// 更改半天假开始
+_this.calTime3 = function (event) {
+  if (event.new_value.alias.split(' ').length === 1 ){
+    event.new_value.alias += ' '+ _this['isHalf' + event.index][event.new_value.value]
+  }
+  getTimeLength(event.index)
+}
+// 更改半天假结束
+_this.calTime4 = function (event) {
+  if (event.new_value.alias.split(' ').length === 1 ){
+    event.new_value.alias +=  ' '+ _this['isHalf' + event.index][event.new_value.value]
+  }
+  getTimeLength(event.index)
+}
+
+// 时长更改时 做校验
+_this.lengthChange = function(event){
+  var value = event.new_value
+//   判断是否等于小于0
+  if (!isNaN(parseFloat(value)) && value <= 0) {
+    mbos.msgBox.showError(localeResource.lengthValid);
+    setTimeout(function(){
+      mbos('entries_leaveLength',event.index).value('')
+    })
+    return
+  }
+  
+}
+//更新整体请假
+_this.isAllDayChange = function(event){
+  var index = event.index
+  _this.setTimeVisiable(index);
+  getTimeLength(index)
+}
+//设置半天假的上午下午选择框可见不可见
+_this.setTimeVisiable = function(index){
+  var isAllDay = mbos("entries_isAllDay",index).value();
+  var isHalf = _this['isHalf' + index] ? _this['isHalf' + index].isHalfDayOff : false;
+  if(isAllDay){
+    if(isHalf){
+      hideHalfDay(index)
+    }
+    _this.changeTime = true
+    beginEndFormat('YYYY-MM-DD', index) 
+    setTimeout(()=>{
+      _this.changeTime = false
+    })
+  }else{
+    if(isHalf){
+      //半天假时 整天请假需要显示时间点,切换为非整天请假时需要将时间点去掉
+      var beginTime = $("#entries_beginTime_"+index+" .ng-pristine").val();
+      var endTime = $("#entries_endTime_"+index+" .ng-pristine").val()
+      $("#entries_beginTime_"+index+" .ng-pristine").val(beginTime ? beginTime.split(" ")[0]:'')
+      $("#entries_endTime_"+index+" .ng-pristine").val(endTime ? endTime.split(" ")[0]:'')
+      setHalfDay(index)
+    }else{
+      hideHalfDay(index)
+    }
+    _this.changeTime = true
+    //半天假和非自定义哺乳假
+    if(isHalf || _this.isCustomBr(index)==="noCustomBf"){
+      beginEndFormat('YYYY-MM-DD', index)
+    }else{
+      _this.setTimeYMDHM('YYYY-MM-DD HH:mm', index)
+    }
+    setTimeout(()=>{
+      _this.changeTime = false
+    })
+  }
+}
+_this.setTimeYMDHM = function(format, index){
+  mbos('entries.beginTime', index).timeFormat(format);
+  mbos('entries.endTime', index).timeFormat(format);
+  var startTimeValue =  mbos('entries.beginTime', index).value();
+  var endTimeValue =  mbos('entries.endTime', index).value();
+  var isAllDay =  mbos('entries.isAllDay', index).value();
+  var endDate = endTimeValue ? endTimeValue.split(" ")[0] : '';
+  var endTime = endTimeValue && endTimeValue.split(" ").length == 2 ? endTimeValue.split(" ")[1] : '';
+
+  var startDate = startTimeValue ? startTimeValue.split(" ")[0] : '';
+  var startTime = startTimeValue && startTimeValue.split(" ").length == 2 ? startTimeValue.split(" ")[1] : '';
+  if(!isAllDay && endTime.indexOf('00:00')!== -1 && startDate===endDate){
+    var endDate = endTimeValue.split(" ")[0];
+    mbos('entries.endTime', index).value(endDate+" 23:59")
+  }
+},
+
+_this.bindDateChange = function(index){
+  $("#entries_beginTime_"+index+" .mobInput").bind('change',function(){
+    var value = mbos("entries_beginTime_"+index).value();
+    value = value ? value.split(" ")[0] : '';
+    mbos("entries_beginYMD_"+index).value(value);
+    //当开始日期大于于用户选的结束日期时将结束时间设置为开始日期
+    var endYMD = mbos("entries_endYMD_"+index).value();
+    if(new Date(endYMD).getTime() < new Date(value).getTime()){
+       mbos("entries_endYMD_"+index).value(value);
+    }
+  })
+  $("#entries_endTime_"+index+" .mobInput").bind('change',function(){
+    var value = mbos("entries_endTime_"+index).value();
+    value = value ? value.split(" ")[0] : '';
+    mbos("entries_endYMD_"+index).value(value);
+    //当结束日期小于用户选的开始日期时将开始时间设置为结束日期
+    var beginYMD = mbos("entries_beginYMD_"+index).value();
+    if(new Date(beginYMD).getTime() > new Date(value).getTime()){
+       mbos("entries_beginYMD_"+index).value(value);
+    }
+  })
+  
+  // 工作流根据请假类型匹配不同的流程定义,请假类型改变后要刷新下一步参与人控件
+mbos("entries.sourceHolidayType",index).bind("change",function(newF7Value) {
+	    var model = mbos('entity').value();
+		var param = $.extend(true, {}, model);
+		var entries = param.entries;
+		if(entries && entries.length>0){
+			entries[index].sourceHolidayType = newF7Value;
+                // 这几个字段导致model转单据实体失败,先去掉待shr排查!
+                        delete entries[index].beginTime;
+                        delete entries[index].beginYMD;
+                        delete entries[index].endTime;
+                        delete entries[index].endYMD;
+		}
+		mbos('nextperson1').getNextPerson(param,null);
+  });
+}
+
+_this.sflsChange = function(event){
+	var index = event.index;
+	_this.controlPlaceRequired(index);
+
+}
+_this.sfcgjChange = function(event){
+	var index = event.index;
+	_this.controlPlaceRequired(index);
+}
+        
+ 
+// 核心:控制目的地必填 + 前端标红 20260507
+_this.controlPlaceRequired = function(index){
+  // 替换成你自己两个布尔字段编码、目的地字段编码
+  var b1 = mbos('entries_sfls', index).value();
+  var b2 = mbos('entries_sfcgj', index).value();
+  var needReq = b1 || b2;
+  
+  // 1. 设置 MBOS 内部必录属性(给后台校验用)
+  mbos('entries_place', index).attr('mustinput', needReq);
+
+  // 2. 找到目的地的 label 标签
+  var $fieldContainer = $('#entries_place_' + index);
+  var $label = $fieldContainer.find('label.lclabel');
+  
+  if (!$label.length) return;
+
+  // 3. 先移除旧的红星(防止重复添加)
+  $label.find('i.mustinput').remove();
+
+  // 4. 需要必填时,添加 MBOS 原生红星 <i class="mustinput">*</i>
+  if (needReq) {
+    $label.append('<i class="mustinput">*</i>');
+  }
+}
+
+
+// ====================== 多分录目的地必填校验(通用方法)======================
+_this.validatePlaceRequired = function(){
+  // 1. 获取所有分录行数
+  var entries = mbos('entity').data.entries;
+  if(!entries || entries.length === 0) return true;
+
+  // 2. 遍历每一行进行校验
+  for(var i = 0; i < entries.length; i++){
+    var sfls = mbos('entries_sfls', i).value() || false;
+    var sfcgj = mbos('entries_sfcgj', i).value() || false;
+    var place = mbos('entries_place', i).value();
+
+    // 3. 判断:任一勾选,但目的地为空
+    if((sfls || sfcgj) && (!place || !place.trim())){
+      // 4. 提示具体是第几行
+      mbos.msgBox.showError('第' + (i+1) + '行:勾选了【是否离深】或【是否出国境】时,请填写目的地!');
+      return false; // 校验不通过,返回 false
+    }
+  }
+  return true; // 所有行都校验通过,返回 true
+};

+ 732 - 0
mbos/mbos_请假单_备份_202605.js

@@ -0,0 +1,732 @@
+_this.pageinit = function () {
+  newPageInit(_this)
+  _this.entryleaveDay={};
+}
+// 	在页面初始化加载之后
+mbos('page').bind('afterLoad', function () {
+  _this.bfTypeValue = "8r0AAAAMhrv9LVKY"; // "8r0AAAA09p9IT3K3"
+  _this.customeBf = "8r0AAAA09p9IT3K3";
+  _this.path = mbos.pageInfo.path// 轻应用的路径编码 例如 trip880
+  _this.detailPageCode = mbos.pageInfo.name.replace('Add', 'View') // 详情页面的编码 (个别页面可特殊处理直接写入) 例如 tripView.editui
+  _this.operateState = mbos.getRequestParams().operateState // 'ADDNEW' 'EDIT' 'VIEW'
+  // 初始化审批流
+  //mbos('nextperson1').checkParticipantPerson({
+  //  "callback": function () { }
+  //});
+  // 单据说明
+  mbos('description').bind('click', function () {
+    handleClickDesc()
+  })
+})
+// 【请假类型】F7过滤, _this.holidayIdList是接口返回的过滤数据,再依据这个过滤F7 
+function filterLeaveTypeF7(index) {
+  var filterStr = _this.holidayIdList.join('\',\'')
+  mbos('entries_sourceHolidayType', index).attr('dynamicFilter', 'id in (\'' + filterStr + '\')'); // 动态添加过滤
+}
+// 哺乳假F7
+function filterBreastTypeF7(index) {  
+  mbos('entries.bfType', index).attr('dynamicFilter', 'state = 1'); // 动态添加过滤
+}
+// 分录渲染后
+mbos('entries').bind('afterRendered', function (e) {
+  _this.leaveLengthTitle = mbos('entries_leaveLength_0').attr('title').split('(')[0] // 获取请假时长的标题
+  _this.bindDateChange(e.index) 
+  _this.getAllDayField(e.index)
+  getHolidayType(e)
+  multiLangField(e.index)// 兼容多语言字段回显的问题
+})
+
+//兼容是否有整天请假字段,并根据分录数据的整天请假的值设置时间格式
+_this.getAllDayField = function (index) {
+  var setAllDayTime = function () {
+    if(_this.hasAllDayField === 'noAllDayField'){
+      mbos('entries_isAllDay',index).hide()
+      mbos('entries_isAllDay',index).value(false)
+    }else {
+      mbos('entries_isAllDay',index).show()
+      _this.setTimeVisiable(index)
+    }
+  }
+  if(!_this.hasAllDayField){
+    var callback = function (res) {
+      _this.hasAllDayField = res.data.isAllDay ? 'hasAllDayField' : 'noAllDayField';
+      setAllDayTime()
+    }
+    baseInterface('getDecimalPlace', callback)
+  }else{
+    setAllDayTime()
+  }
+  
+}
+// 保存按钮
+_this.save = function (event) {
+  var params = getModel(-1, 'save') // 封装model,不走common的封装
+  if(params.entries.length > 1){
+    params.billSourceType = '2'
+  }
+  baseInterface('save', toView, -1, params) // 调用保存接口
+}
+// 提交按钮
+_this.submit = function (event) {
+    // 确认提交 弹框
+  mbos.ui.showConfirm({
+    title: localeResource.confirmSubmit,
+    iconclass: "kdfont kdfont-zhuangtai_jingshi the_info",
+    callback: function (data) {
+      mbos('nextperson1').checkParticipantPerson({
+      "editdata":getModel(-1, 'submit'),
+      "callback": function () {
+          if (data == 0) {
+            var params = getModel(-1, 'submit') // 封装model,不走common的封装
+            baseInterface('submit', toView, -1, params) // 调用提交接口
+          }
+        }
+   	  });
+      
+    }
+  });
+
+
+}
+
+// 点击“保存、提交” 跳转页面
+function toView(res) {
+  // 提交接口 返回的res.data为数组,取第一条数据
+  if (res.data.keyValue || (res.data.length > 0 && res.data[0].keyValue)) {
+        // 操作成功 提示弹框
+        mbos.ui.showInfo({
+      title: localeResource.succeed,
+      iconclass: "kdfont kdfont-zhuangtai_wancheng the_success",
+      callback: function () {
+        openPage(_this.path, _this.detailPageCode, { billID: res.data.keyValue || res.data[0].keyValue })
+      }
+    });
+  }
+}
+// 给隐藏的model赋值
+function setModel() {
+  if (typeof _this.operateState != "undefined" && (_this.operateState === "ADDNEW" || _this.operateState === "EDIT")) {
+    var entries = _.cloneDeep(mbos('entity').data.entries) // lodash 深拷贝
+    entries.map(function (entry, i) {
+      entry.person = easContext.person
+//       position和adminOrgUnit取后端默认值,前端取的是人事档案的不是正确的值
+      // entry.position = easContext.position.id
+      // entry.adminOrgUnit = easContext.position.adminOrgUnit.id
+      getEntries(entry, i) // 各单据处理分录数据
+    })
+    return entries
+  }
+}
+//哺乳假 是自定义还是非自定义
+_this.isCustomBr = function(index){
+  var htype = mbos("entries.sourceHolidayType",index).value() ? mbos("entries.sourceHolidayType",index).value().id : ''
+  var bfType = mbos("entries.bfType",index).value() ? mbos("entries.bfType",index).value().id : ''
+  if(htype && bfType && htype===_this.bfTypeValue && bfType!== _this.customeBf){
+    return 'noCustomBf';
+  }else if(htype && bfType && htype===_this.bfTypeValue && bfType === _this.customeBf){
+    return 'isCustomBf';
+  }
+}
+function getModel(index, method) {
+  var entries = setModel()
+  var obj = mbos('entity').data
+  if (method === "save" || method === "submit"){ // 只在保存喝提交时修改枚举字段的存储,不然会清空表头的枚举字段
+    Object.keys(obj).map(function(key){
+      if (obj[key] && obj[key].isenum) {// 遍历枚举字段 依据后端需要 传参只传value
+        obj[key] = obj[key].value
+      }
+    })
+    console.log(obj)    
+  }
+  for(var i=0; i<entries.length;i++){
+    var item = entries[i];
+    var formate = 'YYYY-MM-DD HH:mm:ss'
+    if(item.beginTime){
+      item.beginTime = moment(item.beginTime).format(formate)
+    }
+    if(item.endTime){
+      item.endTime = moment(item.endTime).format(formate)
+    }
+    if (method === "save" || method === "submit"){
+      if(!item.beginYMD){
+        item.beginYMD = item.beginTime ? item.beginTime.split(" ")[0] : '';
+      }
+      if(!item.endYMD){
+        item.endYMD = item.endTime ? item.endTime.split(" ")[0] : '';
+      }
+      if(_this.isCustomBr(i) === 'noCustomBf' && new Date(item.beginTime.split(" ")[0]) <= new Date(item.mLEndTime)){
+        mbos.msgBox.showError(localeResource.prMsg);
+        throw new Error(localeResource.prMsg)
+      }
+      if(item.leaveLength > _this.entryleaveDay[i]){
+        mbos.msgBox.showError(localeResource.detialNo+(i+1)+localeResource.leaveDaysMsg);
+        throw new Error(localeResource.prMsg)
+      }
+    }
+  }
+ // var model = {
+  //  ...obj,
+  //  entries: entries,
+  //}
+  var model =  $.extend({},obj,{entries: entries})
+  if (index >= 0) { // index 为-1 时代表取所有明细
+    model.entries = [entries[index]]
+  }
+  if ((method === "save" || method === "submit") && mbos.getRequestParams().abnormalId) {
+    model.abnormalId = mbos.getRequestParams().abnormalId
+  }
+  var personList = mbos('personSelector1').$element ? mbos('personSelector1').getpersonList() : []; // 抄送人列表
+  model.ccPersonIds = personList.map(function (i) { return (i.personid || i.id) }).join(',')
+  model.ccPerson = personList.map(function (i) { return (i.personid || i.id) }).join(',')
+  return model
+}
+// 各单据处理分录数据的函数
+function getEntries(entry, i) {
+  if (!entry.bfType) entry.bfType = ''
+  var halfData = _this['isHalf' + i]
+  var beginHalf =  mbos('beginHalf', i).value() ?  mbos('beginHalf', i).value().value : ''
+  if (halfData && entry.beginTime && !entry.beginTime.split(' ')[1] && beginHalf) {
+    // 页面显示的是上午下午,但是传参得拼接上时分 格式为年月日时分
+    //entry.beginTime = entry.beginTime + ' ' + (mbos('beginHalf', i).value().value === 'am' ? halfData.amBeginTime : halfData.pmBeginTime)
+    //entry.endTime = entry.endTime + ' ' + (mbos('endHalf', i).value().value === 'am' ? halfData.amEndTime : halfData.pmEndTime)
+    entry.beginTime = entry.beginTime.split(" ")[0] + ' ' + halfData[mbos('beginHalf', i).value().value]
+    entry.endTime = entry.endTime.split(" ")[0] + ' ' + halfData[mbos('endHalf', i).value().value]
+  }
+  // 如果是哺乳假 非自定义的类型 结束时间需要拼接时分为23:59
+  var _holidayType = mbos('entries_sourceHolidayType', i).value()
+  var _bfType = mbos('entries_bfType', i).value()
+  if (_holidayType && _holidayType.name == localeResource.bf_holiday && _bfType && _bfType.name !== localeResource.Customized){
+    entry.beginTime = entry.beginTime.split(" ")[0] + ' 00:00'
+    entry.endTime = entry.endTime.split(" ")[0] + ' 23:59'
+  }
+   if(_holidayType && _holidayType.name !== localeResource.bf_holiday && entry.isAllDay){
+      if(_this['fullBeginTime_'+i]){
+          entry.beginTime = _this['fullBeginTime_'+i];
+          entry.endTime = _this['fullEndTime_'+i];
+      } else {
+          var beginHMS = _this['beginHMS_'+i] ? _this['beginHMS_'+i] : '00:00';
+          var endHMS = _this['endHMS_'+i] ? _this['endHMS_'+i] : '23:59';
+          entry.beginTime = (entry.beginYMD || '') + " " + beginHMS;
+          entry.endTime = (entry.endYMD || '') + " " + endHMS;
+      }
+  }
+}
+// 获取请假类型
+var getHolidayType = _.debounce(function (e) {
+  var callback = function (res) {
+    var list = res.data
+    _this.holidayIdList = res.data.map(function(item){return  item.holidayTypeId.id}) // 存储过滤后的请假类型F7数据
+    filterLeaveTypeF7(e.index)
+    filterBreastTypeF7(e.index)
+    // 请假类型数组
+    _this.holidayType = {}
+    for (var i = 0; i < list.length; i++) {
+      var typeId = list[i].holidayTypeId.id
+      _this.holidayType[typeId] = {
+        holidayPolicyId: list[i].holidayPolicyInfo.id,
+        sourceUnit: list[i].unit,
+        sourceHolidayType: list[i].holidayTypeId,
+        description: list[i].description
+      }
+    }
+    if (mbos.getRequestParams().type && mbos.getRequestParams().holidayTypeId){ 
+      // 从我的假期页面跳转过来,携带赋值假期类型
+      mbos('entries_sourceHolidayType', e.index).value({
+        id:  decodeURIComponent(mbos.getRequestParams().holidayTypeId),
+        name: mbos.getRequestParams().type
+      })
+    }
+    if (mbos('entries_sourceHolidayType', e.index).value()) {
+      _this.typeChange({
+        index: e.index,
+        new_value: mbos('entries_sourceHolidayType', e.index).value(),
+        old_value: ''
+      })
+    }
+  }
+  var date = mbos.getRequestParams().date || new Date()
+  //  需要传参时间,默认为当天 或其他页面带过来的参数
+  var today = moment(date).format('YYYY-MM-DD')
+  if (!mbos('entries_beginTime', e.index).value()) {
+    mbos('entries_beginTime', e.index).value(today + ' 00:00')
+    mbos('entries_beginYMD', e.index).value(today)
+  }
+  if (!mbos('entries_endTime', e.index).value()) {
+    mbos('entries_endTime', e.index).value(today + ' 23:59')
+    mbos('entries_endYMD', e.index).value(today)
+  }
+  setModel()
+  baseInterface('getHolidayTypeByPerson', callback, e.index)
+}, 0)
+// 接口计算请假时长
+var getTimeLength = _.debounce(function (index) {
+  var callback = function (res) {
+    mbos('entries_leaveLength', index).value(res.data.leaveBillDays)
+    _this.entryleaveDay[index] = res.data.leaveBillDays
+  }
+  var addDayCallback = function (res) {
+      var isHalf = _this['isHalf' + index];
+      mbos('entries_leaveLength', index).value(res.data.leaveLength);
+      _this.entryleaveDay[index] = res.data.leaveBillDays
+      if(isHalf){
+        _this['beginHMS_'+index] = res.data.amStartWorkTime
+        _this['endHMS_'+index] = res.data.pmEndWorkTime
+      }else{
+        _this['beginHMS_'+index] = res.data.beginHMS
+        _this['endHMS_'+index] = res.data.endHMS
+      }
+
+      var beginHMS = isHalf ? res.data.amStartWorkTime : res.data.beginHMS;
+      var endHMS = isHalf ? res.data.pmEndWorkTime : res.data.endHMS;
+      _this['fullBeginTime_'+index] = res.data.beginYMD.split(" ")[0] + " " + beginHMS;
+      _this['fullEndTime_'+index] = res.data.endYMD.split(" ")[0] + " " + endHMS;
+
+      _this.changeTime = true
+      mbos("entries_beginTime_"+index).value(res.data.beginYMD);
+      mbos("entries_endTime_"+index).value(res.data.endYMD);
+      setTimeout(function(){
+        _this.changeTime = false
+        $("#entries_beginTime_"+index+" .ng-pristine").val(_this['fullBeginTime_'+index])
+        $("#entries_endTime_"+index+" .ng-pristine").val(_this['fullEndTime_'+index])
+      })
+  }
+  var params = getModel(index,'changeLeaveBillTime') // 封装model,不走common的封装
+  var entry = mbos('entity').data.entries[index]
+  if (entry.beginTime && entry.endTime && entry.sourceHolidayType) {
+     //整天请假
+     var  holidayTypeId = entry.sourceHolidayType.id;
+     if(entry.isAllDay && holidayTypeId !== _this.bfTypeValue){
+       var param = _.cloneDeep(params);
+       param.entries[0].beginTime =entry.beginYMD ||entry.beginTime.split(" ")[0];
+       param.entries[0].endTime =  entry.endYMD || entry.endTime.split(" ")[0];
+       param.entries[0].holidayTypeId = holidayTypeId
+       param.entries[0].personId = params.entries[0].person.id;
+       baseInterface('changeLeaveBillTime',addDayCallback, index, param)
+     //非整体请假
+     }
+    baseInterface('getBillLength', callback, index, params)
+  }
+}, 500)
+// 获取假期剩余额度单位
+var getRemainLength = _.debounce(function (event) {
+  var index = event.index
+  var entry = mbos('entity').data.entries[index]
+  delete entry.SourceHolidayType
+  if (entry.beginTime && entry.endTime && entry.sourceHolidayType) {
+    var callback = function (res) {
+      if (!res.data) {
+        $('#remainCard_' + index).css('display', 'none')
+        return
+      }
+      if (res && res.data) {
+        var type = _this.holidayType[mbos('entries_sourceHolidayType', index).value().id]
+        $('#remainCard_' + index).css('display', 'flex')
+        //document.getElementById('remainCard_' + index).innerText = `${type.sourceHolidayType.name}  ${localeResource.remained}${res.data.remainLimitVal}${type.sourceUnit.alias} ${localeResource.remainedDays}`
+     	document.getElementById('remainCard_' + index).innerText = type.sourceHolidayType.name+' '+localeResource.remained+res.data.remainLimitVal+type.sourceUnit.alias+localeResource.remainedDays
+
+      }
+    }
+    setModel()
+    baseInterface('getHolidayLimitByHolidayType', callback, index)
+  }
+}, 200)
+// 根据时间判断 是否展示弹性算时长字段
+var isElastic = _.debounce(function (index) {
+  var callback = function (res) {
+    if (res.data) {
+      mbos('entries_isElasticCalLen', index).show()
+    } else {
+      mbos('entries_isElasticCalLen', index).hide()
+    }
+  }
+  //   TODO   holidayPolicyId待添加
+  setModel(index)
+  var entry = mbos('entity').data.entries[index]
+  if (entry.beginTime && entry.endTime) {
+    baseInterface('showIsElasticCalCtrl', callback, index)
+  }
+}, 500)
+
+/**
+ * 修改开始结束时间的格式
+ * YYYY-MM-DD      : 非【自定义】的所有哺乳假类型
+ * YYYY-MM-DD HH:mm:未启用半天假的非哺乳假;【自定义】的哺乳假类型且未启用半天假;
+ * YYYY-MM-DD AM/PM:启用半天假的非哺乳假、【自定义】的哺乳假类型且启用半天假 
+ */
+function beginEndFormat(format, index) {
+  mbos('entries.beginTime', index).timeFormat(format);
+  mbos('entries.endTime', index).timeFormat(format);
+}
+
+// 是否开启半天假
+function isHalfHoliday(index, timeFormat) {
+  var callback = function (res) {
+    if (res.data && res.data.isHalfDayOff) {
+      //       先赋值 在设置半天假 最后格式化时间,顺序不能变
+      _this['isHalf' + index] = res.data
+      // setHalfDay(index)// 设置开启半天假的样式
+      // beginEndFormat('YYYY-MM-DD', index)
+    } else {
+      _this['isHalf' + index] = false
+      // hideHalfDay(index)// 隐藏半天假
+      // beginEndFormat(timeFormat ? timeFormat : 'YYYY-MM-DD HH:mm', index)
+    }
+    _this.setTimeVisiable(index);
+  }
+  setModel()
+  baseInterface('getSetIsCtrlHalfDayOff', callback, index)
+}
+// 设置开启半天假的样式
+function setHalfDay1(index) {
+  $('#entries_beginTime_' + index).removeClass('col-xs-12').addClass('col-xs-9')
+  $('#entries_endTime_' + index).removeClass('col-xs-12').addClass('col-xs-9')
+  // mbos('beginHalf', index).show()
+  // mbos('endHalf', index).show()
+  $('#beginHalf_' + index).css("display", "block")
+  $('#endHalf_' + index).css("display", "block")
+  var half = {
+    am: { alias: localeResource.AM, value: "am" },
+    pm: { alias: localeResource.PM, value: "pm" },
+  }
+  var halfData = _this['isHalf' + index] // 半天假及时间字段
+  // 编辑时 根据时间判断
+  var startHM = halfData.pmBeginTime === mbos('entries_beginTime', index).value().split(' ')[1] ? half.pm : half.am
+  var endHM = halfData.amEndTime === mbos('entries_endTime', index).value().split(' ')[1] ? half.am : half.pm
+  //mbos('beginHalf', index).value(startHM)
+  //mbos('endHalf', index).value(endHM)
+  if(!mbos('beginHalf', index).value()){
+      mbos('beginHalf', index).value(startHM)
+    }
+    if(!mbos('endHalf', index).value()){
+      mbos('endHalf', index).value(endHM)
+    }
+}
+// 设置开启半天假的样式
+function setHalfDay(index) {
+  // 样式变动
+  $('#entries_beginTime_' + index).removeClass('col-xs-12').addClass('col-xs-8')
+  $('#entries_endTime_' + index).removeClass('col-xs-12').addClass('col-xs-8')
+  $('#beginHalf_' + index).css("display", "block")
+  $('#endHalf_' + index).css("display", "block")
+  halfClick(index) // 半天枚举控件的点击事件
+  var halfData = _this['isHalf' + index] // 半天假及时间字段
+  var half = {}
+  half[halfData.amBeginTime] = { alias: localeResource.AM + ' ' + halfData.amBeginTime, value: "amBeginTime" }
+  half[halfData.amEndTime] = { alias: localeResource.AM + ' ' + halfData.amEndTime, value: "amEndTime" }
+  half[halfData.pmBeginTime] = { alias: localeResource.PM + ' ' + halfData.pmBeginTime, value: "pmBeginTime" }
+  half[halfData.pmEndTime] = { alias: localeResource.PM + ' ' + halfData.pmEndTime, value: "pmEndTime" }
+  _this.halfEnum = half
+  // 编辑时 根据时间判断
+  var sTime = mbos('entries_beginTime', index).value().split(' ')[1]
+  var eTime = mbos('entries_endTime', index).value().split(' ')[1]
+  var startHM = half[sTime] || half[halfData.amBeginTime]
+  var endHM = half[eTime] || half[halfData.pmEndTime]
+  
+  mbos('beginHalf', index).value(startHM)
+  mbos('endHalf', index).value(endHM)
+}
+// 半天控件的点击事件 给枚举选项拼接时分
+function halfClick(index){
+  var list = ["amBeginTime","amEndTime","pmBeginTime","pmEndTime"]
+  $('#beginHalf_'+index).click(function () {
+    setTimeout(function(){
+      var enumList = $('.seldiv .ng-binding')
+      for (var i = 0; i < enumList.length; i++) {
+        if(i == 1 || i == 3){
+          enumList.eq(i).closest('li').hide();
+        }
+        enumList[i].innerHTML = enumList[i].innerHTML.split(' ')[0] + ' ' + _this['isHalf' + index][list[i]]
+      }
+    }, 0)
+  })
+  $('#endHalf_'+index).click(function () {
+    setTimeout(function(){
+      var enumList = $('.seldiv .ng-binding')
+      for (var i = 0; i < enumList.length; i++) {
+        enumList.eq(i).closest('li').show();
+        if(i == 0 || i == 2){
+          enumList.eq(i).closest('li').hide();
+        }
+        enumList[i].innerHTML = enumList[i].innerHTML.split(' ')[0] + ' ' + _this['isHalf' + index][list[i]]
+      }
+    }, 0)
+  })
+}
+// 隐藏半天假
+function hideHalfDay(index) {
+  $('#entries_beginTime_' + index).removeClass('col-xs-8').addClass('col-xs-12')
+  $('#entries_endTime_' + index).removeClass('col-xs-8').addClass('col-xs-12')
+  mbos('beginHalf', index).hide()
+  mbos('endHalf', index).hide()
+}
+// 监听请假类型
+_this.typeChange = function (event) {
+  var index = event.index
+  var entry = mbos('entity').data.entries[index]
+  if (!event.new_value) return //清空
+  var type = _this.holidayType && _this.holidayType[event.new_value.id]
+  if (type) {
+    entry.sourceUnit = type.sourceUnit // 1是天 2是小时
+    entry.holidayPolicyId = type.holidayPolicyId
+    entry.SourceHolidayType = type.sourceHolidayType.id
+  }
+  // 是否是哺乳假
+  var sourceHolidayTypeValue = mbos('entries_sourceHolidayType', index).value();
+  if (sourceHolidayTypeValue && (sourceHolidayTypeValue.id ==  _this.bfTypeValue || sourceHolidayTypeValue.name == localeResource.bf_holiday)) {
+    mbos('entries.isAllDay', index).hide()
+    mbos('entries_isAllDay',index).value(false)
+    // 请假类型为【哺乳假】则显示 哺乳假类型、子女出生日期、产假结束日期
+    mbos('entries.bfType', index).show()
+    var format = 'YYYY-MM-DD'
+    if (mbos('entries_bfType', index).value()) {
+      if(mbos('entries_bfType', index).value().name == localeResource.Customized){
+        format = 'YYYY-MM-DD HH:mm'
+      } else {
+        mbos('entries.childbirthday', index).show()
+        mbos('entries.mLEndTime', index).show()
+      }
+    } else {
+	  // 如果哺乳假类型不存在则默认为自定义
+      mbos('entries_bfType', index).value({id: _this.customeBf, name: localeResource.Customized})
+      format = 'YYYY-MM-DD HH:mm'
+    }
+    
+    isHalfHoliday(index, format) // 判断是否启用半天假
+  } else {// 非哺乳假时间格式只有两种:【年月日时分】、【年月日 上/下午】。启用半天假时为【年月日 上/下午】,其他的都是【年月日时分】;
+    // 其他类型则隐藏
+    mbos('entries.bfType', index).hide()
+    mbos('entries_bfType', index).value('')
+    mbos('entries.childbirthday', index).hide()
+    mbos('entries.childbirthday', index).value('')
+    mbos('entries.mLEndTime', index).hide()
+    mbos('entries.mLEndTime', index).value('')
+    if(_this.hasAllDayField === 'hasAllDayField'){
+      mbos('entries.isAllDay', index).show()
+    }
+   
+    isHalfHoliday(index) // 判断是否启用半天假
+  }
+  if (entry.sourceUnit) {
+     mbos('entries_leaveLength', index).attr('title', _this.leaveLengthTitle +`(${entry.sourceUnit.alias})`);
+  }
+  getTimeLength(index)
+  getRemainLength(event)
+  getExplain(index)
+}
+// 监听 开始时间
+_this.calTime1 = function (event) {
+  if(_this.changeTime) return;
+  var beginalue = mbos('entries.beginTime',event.index).value();
+  var endValue = mbos('entries.endTime',event.index).value();
+  var newEndValue = endValue;
+  if(new Date(beginalue).getTime() > new Date(endValue).getTime()){
+    if(endValue && endValue.split(' ').length >= 2){
+      newEndValue = beginalue.split(' ')[0] + ' ' + endValue.split(' ')[1];
+      mbos('entries.endTime',event.index).value(newEndValue.split(' ')[0]+" 23:59");
+    }else{
+      newEndValue = beginalue;
+       mbos('entries.endTime',event.index).value(newEndValue);
+    }
+   
+    mbos("entries.endYMD",event.index).value(newEndValue);
+  }
+  getHolidayType(event)
+  getTimeLength(event.index)
+  isElastic(event.index)
+  getRemainLength(event)
+}
+// 监听 结束时间
+_this.calTime2 = function (event) {
+  if(_this.changeTime) return;
+  var beginalue = mbos('entries.beginTime',event.index).value();
+  var endValue = mbos('entries.endTime',event.index).value();
+  var newBeginValue = beginalue;
+  if(new Date(beginalue).getTime() > new Date(endValue).getTime()){
+    if(beginalue && beginalue.split(' ').length >= 2){
+      newBeginValue = endValue.split(' ')[0] + ' ' + endValue.split(' ')[1];
+      mbos('entries.beginTime',event.index).value(newBeginValue.split(' ')[0]+" 00:00");
+    }else{
+      newBeginValue = endValue;
+      mbos('entries.beginTime',event.index).value(newBeginValue);
+    }
+   
+    mbos("entries.beginYMD",event.index).value(newBeginValue);
+  }
+  getHolidayType(event)
+  getTimeLength(event.index)
+  isElastic(event.index)
+  getRemainLength(event)
+}
+// 监听 子女出生日期
+_this.birthdayChange = function (event) {
+  // 请假开始时间默认值:产假结束日期,再加1天;
+  if (event.new_value) {
+    var value = moment(mbos('entries_childbirthday', event.index).value()).add(1, 'years').format('YYYY-MM-DD')
+    value = moment(value).subtract(1, 'days').format('YYYY-MM-DD')
+    mbos('entries.endTime', event.index).value(value)
+  }
+}
+// 监听 产假结束日期
+_this.mlEndChange = function (event) {
+  // 请假结束时间默认值:子女出生日期延后一年,再减1天;
+  if (event.new_value) {
+    var value = moment(mbos('entries_mLEndTime', event.index).value()).add(1, 'days').format('YYYY-MM-DD')
+    mbos('entries.beginTime', event.index).value(value)
+  }
+}
+// 监听 哺乳假类型
+_this.bfTypeChange = function (event) {
+  var index = event.index
+  // 哺乳假类型是否为【自定义】
+  if (!mbos('entries_bfType', index).value()){
+     // 如果哺乳假类型不存在则默认为自定义
+      setTimeout(function(){
+        mbos('entries_bfType', index).value({id: _this.customeBf, name: localeResource.Customized})
+      }, 0)
+  }
+  if (mbos('entries_bfType', index).value() && (mbos('entries_bfType', index).value().id == _this.customeBf || mbos('entries_bfType', index).value().name == localeResource.Customized)) {
+    //   【自定义】: 则不展示【子女出生日期、产假结束日期】
+    mbos('entries.childbirthday', index).value('')
+    mbos('entries.childbirthday', index).hide()
+    mbos('entries.mLEndTime', index).value('')
+    mbos('entries.mLEndTime', index).hide()
+    // 哺乳假类型为【自定义】时,启用半天假时为【年月日 上/下午】,其他的都是【年月日 时分】
+    isHalfHoliday(index) // 判断是否启用半天假
+  } else {
+    // 非【自定义】的所有哺乳假类型 日期格式为年月日
+    beginEndFormat('YYYY-MM-DD', index)
+    mbos('entries.childbirthday', index).show()
+    mbos('entries.mLEndTime', index).show()
+  }
+  getTimeLength(event.index)
+}
+// 监听是否是弹性算时长
+_this.isElasticChange = function (event) {
+  getTimeLength(event.index)
+}
+// 更改半天假开始
+_this.calTime3 = function (event) {
+  if (event.new_value.alias.split(' ').length === 1 ){
+    event.new_value.alias += ' '+ _this['isHalf' + event.index][event.new_value.value]
+  }
+  getTimeLength(event.index)
+}
+// 更改半天假结束
+_this.calTime4 = function (event) {
+  if (event.new_value.alias.split(' ').length === 1 ){
+    event.new_value.alias +=  ' '+ _this['isHalf' + event.index][event.new_value.value]
+  }
+  getTimeLength(event.index)
+}
+
+// 时长更改时 做校验
+_this.lengthChange = function(event){
+  var value = event.new_value
+//   判断是否等于小于0
+  if (!isNaN(parseFloat(value)) && value <= 0) {
+    mbos.msgBox.showError(localeResource.lengthValid);
+    setTimeout(function(){
+      mbos('entries_leaveLength',event.index).value('')
+    })
+    return
+  }
+  
+}
+//更新整体请假
+_this.isAllDayChange = function(event){
+  var index = event.index
+  _this.setTimeVisiable(index);
+  getTimeLength(index)
+}
+//设置半天假的上午下午选择框可见不可见
+_this.setTimeVisiable = function(index){
+  var isAllDay = mbos("entries_isAllDay",index).value();
+  var isHalf = _this['isHalf' + index] ? _this['isHalf' + index].isHalfDayOff : false;
+  if(isAllDay){
+    if(isHalf){
+      hideHalfDay(index)
+    }
+    _this.changeTime = true
+    beginEndFormat('YYYY-MM-DD', index) 
+    setTimeout(()=>{
+      _this.changeTime = false
+    })
+  }else{
+    if(isHalf){
+      //半天假时 整天请假需要显示时间点,切换为非整天请假时需要将时间点去掉
+      var beginTime = $("#entries_beginTime_"+index+" .ng-pristine").val();
+      var endTime = $("#entries_endTime_"+index+" .ng-pristine").val()
+      $("#entries_beginTime_"+index+" .ng-pristine").val(beginTime ? beginTime.split(" ")[0]:'')
+      $("#entries_endTime_"+index+" .ng-pristine").val(endTime ? endTime.split(" ")[0]:'')
+      setHalfDay(index)
+    }else{
+      hideHalfDay(index)
+    }
+    _this.changeTime = true
+    //半天假和非自定义哺乳假
+    if(isHalf || _this.isCustomBr(index)==="noCustomBf"){
+      beginEndFormat('YYYY-MM-DD', index)
+    }else{
+      _this.setTimeYMDHM('YYYY-MM-DD HH:mm', index)
+    }
+    setTimeout(()=>{
+      _this.changeTime = false
+    })
+  }
+}
+_this.setTimeYMDHM = function(format, index){
+  mbos('entries.beginTime', index).timeFormat(format);
+  mbos('entries.endTime', index).timeFormat(format);
+  var startTimeValue =  mbos('entries.beginTime', index).value();
+  var endTimeValue =  mbos('entries.endTime', index).value();
+  var isAllDay =  mbos('entries.isAllDay', index).value();
+  var endDate = endTimeValue ? endTimeValue.split(" ")[0] : '';
+  var endTime = endTimeValue && endTimeValue.split(" ").length == 2 ? endTimeValue.split(" ")[1] : '';
+
+  var startDate = startTimeValue ? startTimeValue.split(" ")[0] : '';
+  var startTime = startTimeValue && startTimeValue.split(" ").length == 2 ? startTimeValue.split(" ")[1] : '';
+  if(!isAllDay && endTime.indexOf('00:00')!== -1 && startDate===endDate){
+    var endDate = endTimeValue.split(" ")[0];
+    mbos('entries.endTime', index).value(endDate+" 23:59")
+  }
+},
+
+_this.bindDateChange = function(index){
+  $("#entries_beginTime_"+index+" .mobInput").bind('change',function(){
+    var value = mbos("entries_beginTime_"+index).value();
+    value = value ? value.split(" ")[0] : '';
+    mbos("entries_beginYMD_"+index).value(value);
+    //当开始日期大于于用户选的结束日期时将结束时间设置为开始日期
+    var endYMD = mbos("entries_endYMD_"+index).value();
+    if(new Date(endYMD).getTime() < new Date(value).getTime()){
+       mbos("entries_endYMD_"+index).value(value);
+    }
+  })
+  $("#entries_endTime_"+index+" .mobInput").bind('change',function(){
+    var value = mbos("entries_endTime_"+index).value();
+    value = value ? value.split(" ")[0] : '';
+    mbos("entries_endYMD_"+index).value(value);
+    //当结束日期小于用户选的开始日期时将开始时间设置为结束日期
+    var beginYMD = mbos("entries_beginYMD_"+index).value();
+    if(new Date(beginYMD).getTime() > new Date(value).getTime()){
+       mbos("entries_beginYMD_"+index).value(value);
+    }
+  })
+  
+  // 工作流根据请假类型匹配不同的流程定义,请假类型改变后要刷新下一步参与人控件
+mbos("entries.sourceHolidayType",index).bind("change",function(newF7Value) {
+	    var model = mbos('entity').value();
+		var param = $.extend(true, {}, model);
+		var entries = param.entries;
+		if(entries && entries.length>0){
+			entries[index].sourceHolidayType = newF7Value;
+                // 这几个字段导致model转单据实体失败,先去掉待shr排查!
+                        delete entries[index].beginTime;
+                        delete entries[index].beginYMD;
+                        delete entries[index].endTime;
+                        delete entries[index].endYMD;
+		}
+		mbos('nextperson1').getNextPerson(param,null);
+  });
+}
+        

+ 101 - 0
websrc/com/kingdee/eas/hr/emp/web/handler/EmployeeListHandlerEx.java

@@ -0,0 +1,101 @@
+package com.kingdee.eas.hr.emp.web.handler;
+
+import com.kingdee.bos.Context;
+import com.kingdee.eas.basedata.person.PersonInfo;
+import com.kingdee.eas.hr.emp.WordTemplateResumeType;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.PrintResumeWordInfo;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.ResumeWordTemplateService;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.impl.ResumeWordTemplateServiceImplEx;
+import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
+import org.apache.log4j.Logger;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * 员工列表 Handler 二开(extends 标品 EmployeeListHandler,不覆盖同名 Factory 类)。
+ * <p><b>状态:代码已编写,尚未在环境中验证 HandlerEx 是否被 SHR 框架自动加载。</b></p>
+ *
+ * <h3>作用</h3>
+ * 履历 Word 导出时,将 {@link #generateWordResumeFileBytes} 中的服务实现
+ * 从标品 Factory 改为 {@link ResumeWordTemplateServiceImplEx},以启用 PersonFamily 排序等逻辑。
+ *
+ * <h3>覆盖的标品入口</h3>
+ * <ul>
+ *   <li>printResumeWordByPersonAction</li>
+ *   <li>printResumeWordByAdminAction</li>
+ * </ul>
+ * 上述 Action 最终都会调用 generateWordResumeFileBytes。
+ *
+ * <h3>【待您确认 / 部署检查】</h3>
+ * <ol>
+ *   <li>SHR 是否自动用 EmployeeListHandlerEx 替换 EmployeeListHandler?(请对照项目中其他 HandlerEx 的加载方式)</li>
+ *   <li>编译产物是否已部署到 Web 应用 WEB-INF/classes,且 classloader 优先于 employee.jar?</li>
+ *   <li>generateResumeWordDocument 第 5 参数 scheme 固定传 "001",【待确认】是否与你们结构配置方案编号一致?</li>
+ *   <li>calNeedTimeAction 等未覆盖的方法仍走标品 Factory,不受本扩展影响(一般无影响)</li>
+ * </ol>
+ */
+public class EmployeeListHandlerEx extends EmployeeListHandler {
+
+    private static final Logger LOGGER = Logger.getLogger(EmployeeListHandlerEx.class);
+
+    /** 履历 Word 服务,固定返回二开实现。 */
+    protected ResumeWordTemplateService getResumeWordTemplateService() {
+        return ResumeWordTemplateServiceImplEx.instance();
+    }
+
+    /**
+     * 复制标品逻辑,唯一差异:通过 getResumeWordTemplateService() 使用 ResumeWordTemplateServiceImplEx。
+     * 单人导出直接返回 doc 字节;多人导出 zip 包。
+     */
+    @Override
+    protected byte[] generateWordResumeFileBytes(
+            Context ctx,
+            HttpServletRequest request,
+            PrintResumeWordInfo printResumeWordInfo
+    ) throws ShrWebBizException {
+        String wordTemplateId = printResumeWordInfo.getWordTemplateInfo().getId().toString();
+        Map<String, PersonInfo> personIdMap = printResumeWordInfo.getPersonIdMap();
+        ResumeWordTemplateService service = getResumeWordTemplateService();
+
+        if (WordTemplateResumeType.PERSON.equals(printResumeWordInfo.getType())
+                && personIdMap != null && personIdMap.size() == 1) {
+            for (Map.Entry<String, PersonInfo> entry : personIdMap.entrySet()) {
+                return service.generateResumeWordDocument(
+                        ctx, request, entry.getKey(), wordTemplateId, "001");
+            }
+            return null;
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ZipOutputStream zipOut = new ZipOutputStream(baos);
+        try {
+            zipOut.setEncoding("UTF-8");
+            if (personIdMap != null) {
+                for (Map.Entry<String, PersonInfo> entry : personIdMap.entrySet()) {
+                    PersonInfo personInfo = entry.getValue();
+                    String fileName = getPersonWordFileName(ctx, personInfo).toString();
+                    zipOut.putNextEntry(new ZipEntry(fileName));
+                    byte[] bytes = service.generateResumeWordDocument(
+                            ctx, request, entry.getKey(), wordTemplateId, "001");
+                    zipOut.write(bytes, 0, bytes.length);
+                }
+            }
+            zipOut.closeEntry();
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new ShrWebBizException(e.getMessage(), e);
+        } finally {
+            try {
+                zipOut.close();
+            } catch (IOException e) {
+                LOGGER.warn("io close fail!");
+            }
+        }
+        return baos.toByteArray();
+    }
+}

+ 292 - 0
websrc/com/kingdee/eas/hr/emp/web/handler/wordtemplate/service/impl/ResumeWordTemplateServiceImplEx.java

@@ -0,0 +1,292 @@
+package com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.impl;
+
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.Context;
+import com.kingdee.eas.hr.base.SqlParam;
+import com.kingdee.eas.common.EASBizException;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.ResumeWordTemplateService;
+import com.kingdee.jdbc.rowset.IRowSet;
+import com.kingdee.shr.baseconfig.StructureConfigColumnsInfo;
+import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
+import com.kingdee.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 员工履历 Word 导出 — 服务层二开扩展。
+ * <p>
+ * 按 familyRelation.number(与本人关系基础资料编码 FNumber)升序排序。
+ * personFamily.relation.number 在 SQL 中会映射为 FRelationID(内码),不能用于按编码排序。
+ * </p>
+ */
+public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceImpl {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ResumeWordTemplateServiceImplEx.class);
+
+    /** Word / 结构配置中 PersonFamily 实体别名(匹配时忽略大小写) */
+    private static final String PERSON_FAMILY_ENTITY = "PERSONFAMILY";
+
+    /**
+     * 与本人关系编码排序字段。
+     * SQL 中 personFamily.relation.number → PERSONFAMILY.FRelationID(UUID),不是编码;
+     * familyRelation.number → T_HR_BDRelation.FNumber(01/02/07777),与 Word 中 familyRelation.name 同源。
+     */
+    private static final String PERSON_FAMILY_SORT_FIELD = "familyRelation.number";
+
+    /** multiNotOne 内存排序临时键,不写入 Word */
+    private static final String ROW_SORT_VALUE = "__sortValue";
+
+    private static final int PERSON_FAMILY_ROW_LIMIT = 0;
+
+    private static volatile ResumeWordTemplateService INSTANCE;
+
+    public static ResumeWordTemplateService instance() {
+        if (INSTANCE == null) {
+            synchronized (ResumeWordTemplateServiceImplEx.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new ResumeWordTemplateServiceImplEx();
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    @Override
+    protected void rowHandle(
+            Context ctx,
+            SqlParam sqlParam,
+            Map<String, String> selectKeyToWordKeyMap,
+            Map<String, Object> resultMap,
+            Map<String, StructureConfigColumnsInfo> columnsInfoMap
+    ) throws EASBizException, BOSException, ShrWebBizException, SQLException {
+        if (containsPersonFamilyEntity(sqlParam)) {
+            LOGGER.info("ResumeWordTemplateServiceImplEx.rowHandle: apply PersonFamily sort before query, field="
+                    + PERSON_FAMILY_SORT_FIELD);
+            preparePersonFamilySortBeforeQuery(sqlParam);
+        }
+        super.rowHandle(ctx, sqlParam, selectKeyToWordKeyMap, resultMap, columnsInfoMap);
+    }
+
+    @Override
+    protected void sorterHandler(Context ctx, SqlParam param) {
+        super.sorterHandler(ctx, param);
+        applyPersonFamilySorter(param);
+    }
+
+    private void preparePersonFamilySortBeforeQuery(SqlParam param) {
+        applyPersonFamilySorter(param);
+    }
+
+    @Override
+    protected void multiNotOne(
+            IRowSet rowSet,
+            Context ctx,
+            SqlParam sqlParam,
+            Map<String, String> selectKeyToWordKeyMap,
+            Map<String, Object> resultMap,
+            Map<String, StructureConfigColumnsInfo> columnsInfoMap
+    ) throws SQLException {
+        List<Map<String, String>> rowList = new ArrayList<Map<String, String>>();
+        String entityKey = null;
+        String sortSelectKey = resolveSortSelectKey(selectKeyToWordKeyMap, PERSON_FAMILY_SORT_FIELD);
+        boolean personFamilyQuery = containsPersonFamilyEntity(sqlParam);
+        boolean sortInMemory = personFamilyQuery && sortSelectKey != null;
+
+        while (rowSet.next()) {
+            Map<String, String> rowData = new HashMap<String, String>();
+            for (Map.Entry<String, String> entry : selectKeyToWordKeyMap.entrySet()) {
+                String wordKey = entry.getValue();
+                Object dbValue = rowSet.getObject(entry.getKey());
+                String displayValue = dataTypeHandle(
+                        ctx,
+                        dbValue,
+                        columnsInfoMap.get(wordKey)
+                );
+                if (!StringUtils.isEmpty(displayValue)) {
+                    rowData.put(wordKey, displayValue);
+                }
+                if (StringUtils.isEmpty(entityKey)) {
+                    int splitIndex = wordKey.indexOf('_');
+                    if (splitIndex > 0) {
+                        entityKey = wordKey.substring(0, splitIndex);
+                    }
+                }
+            }
+            if (sortInMemory) {
+                rowData.put(ROW_SORT_VALUE, resolveRowSortValue(rowSet, sortSelectKey));
+            }
+            if (rowData.size() > 0) {
+                rowList.add(rowData);
+            }
+        }
+
+        if (containsPersonFamilyEntity(sqlParam) && sortInMemory) {
+            sortRowList(rowList);
+            logSortPreview(rowList);
+            for (Map<String, String> rowData : rowList) {
+                rowData.remove(ROW_SORT_VALUE);
+            }
+        } else if (personFamilyQuery) {
+            LOGGER.warn("ResumeWordTemplateServiceImplEx.multiNotOne: PersonFamily detected but sortSelectKey missing, "
+                    + "entityKey=" + entityKey);
+        }
+
+        applyPersonFamilyRowLimit(entityKey, rowList);
+
+        if (rowList.size() > 1) {
+            resultMap.put(entityKey, rowList);
+        } else if (rowList.size() == 1) {
+            resultMap.putAll(rowList.get(0));
+        }
+    }
+
+    private void applyPersonFamilySorter(SqlParam param) {
+        if (!containsPersonFamilyEntity(param)) {
+            return;
+        }
+        param.setSortStr(PERSON_FAMILY_SORT_FIELD + " asc");
+        addSortFieldToSelectMapping(param.getSelectMapping(), PERSON_FAMILY_SORT_FIELD);
+        LOGGER.info("ResumeWordTemplateServiceImplEx: sortStr=" + param.getSortStr());
+    }
+
+    private boolean containsPersonFamilyEntity(SqlParam param) {
+        if (containsEntity(param.getInfoCtr(), PERSON_FAMILY_ENTITY)) {
+            return true;
+        }
+        Map<String, String> selectMapping = param.getSelectMapping();
+        if (selectMapping == null) {
+            return false;
+        }
+        for (String key : selectMapping.keySet()) {
+            if (isPersonFamilyFieldKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isPersonFamilyFieldKey(String key) {
+        if (key == null) {
+            return false;
+        }
+        return key.toUpperCase().startsWith(PERSON_FAMILY_ENTITY + ".");
+    }
+
+    private boolean isPersonFamilyEntityKey(String entityKey) {
+        return entityKey != null && PERSON_FAMILY_ENTITY.equalsIgnoreCase(entityKey);
+    }
+
+    private boolean containsEntity(Map infoCtr, String entityName) {
+        if (infoCtr == null || StringUtils.isEmpty(entityName)) {
+            return false;
+        }
+        if (infoCtr.containsKey(entityName) || infoCtr.containsKey(entityName.toUpperCase())) {
+            return true;
+        }
+        for (Object key : infoCtr.keySet()) {
+            if (entityName.equalsIgnoreCase(String.valueOf(key))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addSortFieldToSelectMapping(Map<String, String> selectMapping, String sortField) {
+        if (StringUtils.isEmpty(sortField) || selectMapping == null) {
+            return;
+        }
+        if (!containsKeyIgnoreCase(selectMapping, sortField)) {
+            selectMapping.put(sortField, sortField);
+        }
+    }
+
+    private String resolveSortSelectKey(Map<String, String> selectKeyToWordKeyMap, String sortField) {
+        if (selectKeyToWordKeyMap == null || StringUtils.isEmpty(sortField)) {
+            return null;
+        }
+        for (String key : selectKeyToWordKeyMap.keySet()) {
+            if (key != null && key.equalsIgnoreCase(sortField)) {
+                return key;
+            }
+        }
+        String suffix = ".relation.number";
+        for (String key : selectKeyToWordKeyMap.keySet()) {
+            if (key != null && key.toUpperCase().endsWith(suffix.toUpperCase())) {
+                return key;
+            }
+        }
+        if (sortField != null && sortField.toUpperCase().endsWith(".NUMBER")) {
+            for (String key : selectKeyToWordKeyMap.keySet()) {
+                if (key != null && key.toUpperCase().endsWith(".NUMBER")) {
+                    return key;
+                }
+            }
+        }
+        return null;
+    }
+
+    private String resolveRowSortValue(IRowSet rowSet, String sortSelectKey) throws SQLException {
+        if (!StringUtils.isEmpty(sortSelectKey)) {
+            Object value = rowSet.getObject(sortSelectKey);
+            if (value != null && !StringUtils.isEmpty(String.valueOf(value))) {
+                return String.valueOf(value).trim();
+            }
+        }
+        return "";
+    }
+
+    private void logSortPreview(List<Map<String, String>> rowList) {
+        if (rowList == null || rowList.isEmpty()) {
+            return;
+        }
+        StringBuilder preview = new StringBuilder();
+        for (Map<String, String> row : rowList) {
+            if (preview.length() > 0) {
+                preview.append(" | ");
+            }
+            preview.append(row.get(ROW_SORT_VALUE));
+        }
+        LOGGER.info("ResumeWordTemplateServiceImplEx.multiNotOne: sorted by " + PERSON_FAMILY_SORT_FIELD
+                + ", sortValues=[" + preview + "], count=" + rowList.size());
+    }
+
+    private void sortRowList(List<Map<String, String>> rowList) {
+        if (rowList == null || rowList.size() <= 1) {
+            return;
+        }
+        Collections.sort(rowList, new Comparator<Map<String, String>>() {
+            @Override
+            public int compare(Map<String, String> left, Map<String, String> right) {
+                String leftValue = left.get(ROW_SORT_VALUE);
+                String rightValue = right.get(ROW_SORT_VALUE);
+                if (leftValue == null) {
+                    leftValue = "";
+                }
+                if (rightValue == null) {
+                    rightValue = "";
+                }
+                return leftValue.compareTo(rightValue);
+            }
+        });
+    }
+
+    private void applyPersonFamilyRowLimit(String entityKey, List<Map<String, String>> rowList) {
+        if (PERSON_FAMILY_ROW_LIMIT <= 0 || rowList == null || rowList.isEmpty()) {
+            return;
+        }
+        if (!isPersonFamilyEntityKey(entityKey)) {
+            return;
+        }
+        if (rowList.size() > PERSON_FAMILY_ROW_LIMIT) {
+            rowList.subList(PERSON_FAMILY_ROW_LIMIT, rowList.size()).clear();
+        }
+    }
+}