wlzboy
2025-12-03 c6e38b6c66de5f5a8df5b8b2ab03a82c3b605db8
feat:优化同步
20个文件已修改
2241 ■■■■■ 已修改文件
app/pagesTask/detail.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit-emergency.vue 226 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit-welfare.vue 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit.vue 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java 519 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IAdditionalFeeSyncService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java 89 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskPaymentServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java 633 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/LegacyTransferSyncMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysTaskAdditionalFeeMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/task/general/detail.vue 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/detail.vue
@@ -28,9 +28,44 @@
          <view class="label">执行车辆</view>
          <view class="value">{{ getVehicleInfo(taskDetail) }}</view>
        </view>
        <view class="info-item">
          <view class="label">执行人员</view>
          <view class="value">{{ taskDetail.assigneeName || '未分配' }}</view>
      </view>
      <!-- 执行人员列表 -->
      <view class="detail-section">
        <view class="section-title">执行人员</view>
        <view v-if="taskDetail.assignees && taskDetail.assignees.length > 0" class="assignee-list">
          <view
            class="assignee-item"
            v-for="(assignee, index) in taskDetail.assignees"
            :key="assignee.userId || index"
          >
            <view class="assignee-index">{{ index + 1 }}</view>
            <view class="assignee-info">
              <view class="assignee-name">
                {{ assignee.userName }}
                <view v-if="assignee.isPrimary === '1'" class="primary-badge">
                  <uni-icons type="star-filled" size="12" color="#ff9500"></uni-icons>
                  <text>负责人</text>
                </view>
              </view>
              <view class="assignee-role">
                <view
                  class="role-tag"
                  :class="{
                    'role-driver': assignee.userType === 'driver',
                    'role-doctor': assignee.userType === 'doctor',
                    'role-nurse': assignee.userType === 'nurse'
                  }"
                >
                  {{ getUserTypeLabel(assignee.userType) }}
                </view>
              </view>
            </view>
          </view>
        </view>
        <view v-else class="empty-assignee">
          <uni-icons type="info" size="40" color="#ccc"></uni-icons>
          <text>暂无执行人员</text>
        </view>
      </view>
      
@@ -531,12 +566,12 @@
        getTask(this.taskId).then(response => {
          this.taskDetail = response.data || response
          // 调试:打印返回的数据
          console.log('任务详情完整数据:', JSON.stringify(this.taskDetail, null, 2))
          console.log('任务类型字段值:', this.taskDetail.taskType)
          console.log('任务状态字段值:', this.taskDetail.taskStatus)
          console.log('出发地址:', this.taskDetail.departureAddress)
          console.log('目的地址:', this.taskDetail.destinationAddress)
          console.log('转运任务信息 (emergencyInfo):', this.taskDetail.emergencyInfo)
          // console.log('任务详情完整数据:', JSON.stringify(this.taskDetail, null, 2))
          // console.log('任务类型字段值:', this.taskDetail.taskType)
          // console.log('任务状态字段值:', this.taskDetail.taskStatus)
          // console.log('出发地址:', this.taskDetail.departureAddress)
          // console.log('目的地址:', this.taskDetail.destinationAddress)
          // console.log('转运任务信息 (emergencyInfo):', this.taskDetail.emergencyInfo)
          
          // 如果是转运任务,加载支付信息
          if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') {
@@ -600,9 +635,9 @@
        return remaining > 0 ? remaining.toFixed(2) : '0.00'
      },
      
      // 获取车辆信息
      // 获取车辆信息(修复:防止 assignedVehicles 为 null)
      getVehicleInfo(task) {
        if (task.assignedVehicles && task.assignedVehicles.length > 0) {
        if (task.assignedVehicles && Array.isArray(task.assignedVehicles) && task.assignedVehicles.length > 0) {
          const firstVehicle = task.assignedVehicles[0]
          let vehicleInfo = firstVehicle.vehicleNo || '未知车牌'
          if (task.assignedVehicles.length > 1) {
@@ -705,6 +740,16 @@
        return typeMap[type] || '未知类型'
      },
      
      // 获取用户类型标签
      getUserTypeLabel(userType) {
        const typeMap = {
          'driver': '司机',
          'doctor': '医生',
          'nurse': '护士'
        }
        return typeMap[userType] || userType || '未知'
      },
      // 处理结算
      handleSettlement() {
        uni.navigateTo({
@@ -776,8 +821,8 @@
          
          const activeTasks = response.data || [];
          
          // 过滤掉当前任务本身
          const otherActiveTasks = activeTasks.filter(task => task.taskId !== this.taskId);
          // 过滤掉当前任务本身(修复:防止 activeTasks 为 null)
          const otherActiveTasks = (activeTasks && Array.isArray(activeTasks)) ? activeTasks.filter(task => task.taskId !== this.taskId) : [];
          
          if (otherActiveTasks.length > 0) {
            // 车辆有其他正在进行中的任务
@@ -1225,6 +1270,104 @@
        }
      }
      
      // 执行人员列表样式
      .assignee-list {
        .assignee-item {
          display: flex;
          align-items: center;
          padding: 20rpx;
          margin-bottom: 15rpx;
          background-color: #f9f9f9;
          border-radius: 10rpx;
          &:last-child {
            margin-bottom: 0;
          }
          .assignee-index {
            width: 50rpx;
            height: 50rpx;
            border-radius: 50%;
            background-color: #007AFF;
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 24rpx;
            font-weight: bold;
            margin-right: 20rpx;
            flex-shrink: 0;
          }
          .assignee-info {
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 10rpx;
            .assignee-name {
              display: flex;
              align-items: center;
              font-size: 30rpx;
              color: #333;
              font-weight: 500;
              .primary-badge {
                display: inline-flex;
                align-items: center;
                gap: 4rpx;
                margin-left: 12rpx;
                padding: 4rpx 12rpx;
                background-color: #fff3e0;
                border-radius: 6rpx;
                text {
                  font-size: 20rpx;
                  color: #ff9500;
                  font-weight: normal;
                }
              }
            }
            .assignee-role {
              .role-tag {
                display: inline-block;
                padding: 4rpx 12rpx;
                border-radius: 6rpx;
                font-size: 22rpx;
                color: white;
                &.role-driver {
                  background-color: #007AFF;
                }
                &.role-doctor {
                  background-color: #34C759;
                }
                &.role-nurse {
                  background-color: #AF52DE;
                }
              }
            }
          }
        }
      }
      .empty-assignee {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 60rpx 0;
        color: #999;
        text {
          margin-top: 20rpx;
          font-size: 28rpx;
        }
      }
      .info-item {
        display: flex;
        margin-bottom: 20rpx;
app/pagesTask/edit-emergency.vue
@@ -27,6 +27,16 @@
        />
      </view>
      
      <DepartureSelector
        :address.sync="departureAddress"
        :longitude.sync="departureLongitude"
        :latitude.sync="departureLatitude"
        :region="selectedRegion"
        :required="false"
        @address-selected="onDepartureAddressSelected"
        @location-success="onDepartureLocationSuccess"
      />
      <view class="form-item">
        <view class="form-label required">转运时间</view>
        <uni-datetime-picker 
@@ -278,6 +288,7 @@
import OrganizationSelector from './components/OrganizationSelector.vue'
import HospitalSelector from './components/HospitalSelector.vue'
import DiseaseSelector from './components/DiseaseSelector.vue'
import DepartureSelector from './components/DepartureSelector.vue'
import distanceCalculator from '@/mixins/distanceCalculator.js'
export default {
@@ -288,7 +299,8 @@
    VehicleSelector,
    OrganizationSelector,
    HospitalSelector,
    DiseaseSelector
    DiseaseSelector,
    DepartureSelector
  },
  mixins: [distanceCalculator],
  data() {
@@ -300,12 +312,19 @@
      selectedOrganizationId: null,
      selectedRegion: '',
      mapSelectorType: '',
      // 扩展 addressCoordinates 支持多种键名
      addressCoordinates: {
        start: null,
        end: null,
        hospitalOutAddress: null,
        hospitalInAddress: null
      },
      // 出发地信息
      departureAddress: '',
      departureLongitude: null,
      departureLatitude: null,
      selectedDiseases: [], // 已选择的病情列表
      selectedStaff: [], // 已选择的人员列表
      selectedDiseases: [], // 已选择的病情列表(确保初始化为空数组)
      selectedStaff: [], // 已选择的人员列表(确保初始化为空数组)
      allStaffList: [], // 所有人员列表
      filteredStaffList: [], // 过滤后的人员列表
      staffSearchKeyword: '', // 人员搜索关键词
@@ -403,8 +422,8 @@
          this.taskForm.patient.idCard = info.patientIdCard || ''
          this.taskForm.patient.condition = info.patientCondition || ''
          
          // 解析病情信息
          this.parseDiseaseInfo(info.patientCondition, info.diseaseIds)
          // 解析病情信息(修复:使用与创建界面一致的逻辑)
          this.parseDiseaseInfo(info.patientCondition, info.diseases)
          
          // 转出医院信息
          this.taskForm.hospitalOut.id = info.hospitalOutId || null
@@ -489,8 +508,8 @@
          console.log('设置目标地坐标:', this.taskDetail.destinationLongitude, this.taskDetail.destinationLatitude)
        }
        
        // 设置执行人员
        if (this.taskDetail.assignees && this.taskDetail.assignees.length > 0) {
        // 设置执行人员(修复:确保 assignees 不为 null)
        if (this.taskDetail.assignees && Array.isArray(this.taskDetail.assignees) && this.taskDetail.assignees.length > 0) {
          console.log('原始执行人员数据:', this.taskDetail.assignees)
          this.selectedStaff = this.taskDetail.assignees.map(assignee => ({
            userId: assignee.userId,
@@ -503,6 +522,8 @@
        } else {
          console.warn('任务没有分配执行人员或assignees为空')
          console.log('taskDetail.assignees:', this.taskDetail.assignees)
          // 确保 selectedStaff 初始化为空数组
          this.selectedStaff = []
        }
        
        console.log('表单数据填充完成:', this.taskForm)
@@ -523,9 +544,33 @@
    
    // 归属机构选择变化
    onOrganizationChange(orgData) {
      // orgData 包含:deptId, deptName, serviceOrderClass, region
      // orgData 包含:deptId, deptName, serviceOrderClass, region, departureAddress, departureLongitude, departureLatitude
      this.selectedOrganizationId = orgData.deptId
      console.log('选中归属机构:', orgData.deptName, '部门ID:', orgData.deptId)
      this.selectedRegion = orgData.region
      // 自动填充出发地信息(机构的地址和坐标)
      if (orgData.departureAddress) {
        this.departureAddress = orgData.departureAddress
        this.departureLongitude = orgData.departureLongitude || null
        this.departureLatitude = orgData.departureLatitude || null
        console.log('自动填充机构出发地:', this.departureAddress, '坐标:', this.departureLongitude, this.departureLatitude)
      }
      console.log('选中归属机构:', orgData.deptName, '部门ID:', orgData.deptId, '地域:', orgData.region)
    },
    // 出发地地址选择(从地图建议中选择)
    onDepartureAddressSelected(data) {
      // data 包含: address, longitude, latitude, location
      console.log('出发地地址选择:', data)
      // 组件已经通过 .sync 更新了 departureAddress, departureLongitude, departureLatitude
    },
    // 出发地GPS定位成功
    onDepartureLocationSuccess(data) {
      // data 包含: address, longitude, latitude
      console.log('出发地GPS定位成功:', data)
      // 组件已经通过 .sync 更新了 departureAddress, departureLongitude, departureLatitude
    },
    
    // 转出医院变化
@@ -732,61 +777,101 @@
      this.selectedStaff.splice(index, 1)
    },
    
    // 解析病情信息(从字符串解析出ICD-10疾病列表)
    parseDiseaseInfo(conditionText, diseaseIds) {
    // 解析病情信息(从字符串解析出ICD-10疾病列表)- 修复:与创建界面保持一致
    parseDiseaseInfo(conditionText, diseases) {
      console.log('========== 开始解析病情信息 ==========')
      console.log('conditionText:', conditionText)
      console.log('diseases数组:', diseases)
      if (!conditionText) {
        console.log('病情文本为空,清空选中病情')
        this.selectedDiseases = []
        this.taskForm.patient.otherCondition = ''
        return
      }
      // 解析diseaseIds(逗号分隔的字符串转为数组)
      const diseaseIdArray = diseaseIds ? diseaseIds.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id)) : []
      
      // 如果包含"其他:"分隔符,拆分病情和其他描述
      if (conditionText.includes('\n其他:')) {
        const parts = conditionText.split('\n其他:')
        const diseasePart = parts[0]
        this.taskForm.patient.otherCondition = parts[1] || ''
        console.log('病情部分:', diseasePart)
        console.log('其他描述:', this.taskForm.patient.otherCondition)
        
        // 解析病情部分
        this.parseDiseaseList(diseasePart, diseaseIdArray)
        this.parseDiseaseList(diseasePart, diseases)
      } else {
        // 没有"其他"部分,全部作为其他描述
        this.taskForm.patient.otherCondition = conditionText
        this.selectedDiseases = []
        // 没有"其他"部分,尝试解析是否有疾病列表
        const hasDiseasesFormat = /([^(]+)\(([^)]+)\)/.test(conditionText)
        console.log('是否包含疾病格式:', hasDiseasesFormat)
        if (hasDiseasesFormat) {
          // 有疾病格式,解析疾病列表
          this.parseDiseaseList(conditionText, diseases)
          this.taskForm.patient.otherCondition = ''
        } else {
          // 没有疾病格式,全部作为其他描述
          console.log('无疾病格式,作为其他描述')
          this.taskForm.patient.otherCondition = conditionText
          this.selectedDiseases = []
        }
      }
      console.log('解析完成,selectedDiseases:', JSON.stringify(this.selectedDiseases))
      console.log('解析完成,otherCondition:', this.taskForm.patient.otherCondition)
      console.log('========================================\n')
    },
    
    // 解析病情列表(格式:疾病名(编码)、疾病名(编码))
    parseDiseaseList(diseasePart, diseaseIdArray) {
    parseDiseaseList(diseasePart, diseasesData) {
      console.log('--- parseDiseaseList 开始 ---')
      console.log('diseasePart:', diseasePart)
      console.log('diseasesData:', diseasesData)
      if (!diseasePart) {
        console.log('病情部分为空')
        this.selectedDiseases = []
        return
      }
      
      // 如果后端已经返回了diseases数组,直接使用
      if (diseasesData && Array.isArray(diseasesData) && diseasesData.length > 0) {
        console.log('使用后端返回的diseases数组,数量:', diseasesData.length)
        this.selectedDiseases = diseasesData.map(d => ({
          id: d.icdId || null,
          icdCode: d.icdCode || '',
          icdName: d.icdName || ''
        }))
        console.log('解析后的selectedDiseases:', JSON.stringify(this.selectedDiseases))
        return
      }
      // 否则,从字符串解析
      console.log('从字符串解析病情')
      // 匹配格式:疾病名(编码)
      const regex = /([^(]+)\(([^)]+)\)/g
      const diseases = []
      let match
      let index = 0
      
      while ((match = regex.exec(diseasePart)) !== null) {
        const icdName = match[1].replace(/[、,\s]+$/, '').replace(/^[、,\s]+/, '').trim()
        const icdCode = match[2].trim()
        
        console.log('匹配到病情 - 名称:', icdName, '编码:', icdCode)
        // 只添加病情名称不为空的项
        if (icdName) {
          diseases.push({
            icdName: icdName,
            id: null, // 从字符串解析时,没有ID
            icdCode: icdCode,
            id: diseaseIdArray && index < diseaseIdArray.length ? diseaseIdArray[index] : null
            icdName: icdName
          })
          index++
        }
      }
      
      console.log('从字符串解析完成,数量:', diseases.length)
      this.selectedDiseases = diseases
      console.log('--- parseDiseaseList 结束 ---\n')
    },
    
    // 选择转出医院地址
@@ -889,6 +974,14 @@
    },
    
    buildSubmitData() {
      // 确保 selectedDiseases 和 selectedStaff 不为 null(修复:防止迭代 null 导致错误)
      if (!this.selectedDiseases) {
        this.selectedDiseases = []
      }
      if (!this.selectedStaff) {
        this.selectedStaff = []
      }
      // 合并病情信息:选中的ICD-10疾病 + 其他描述
      let conditionText = ''
      if (this.selectedDiseases.length > 0) {
@@ -909,47 +1002,46 @@
        }
      }
      
      // 构建提交数据 - 使用与 TaskCreateVO 一致的结构
      const submitData = {
        taskId: this.taskId,
        taskType: 'EMERGENCY_TRANSFER',
        deptId: this.selectedOrganizationId,
        vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [],
        plannedStartTime: this.taskForm.transferTime,
        // 出发地使用 departureAddress(如果有),否则使用转出医院地址
        // 出发地地址和坐标(使用转出医院地址)
        departureAddress: this.departureAddress || this.taskForm.hospitalOut.address,
        // 目标地使用转入医院地址
        departureLongitude: this.departureLongitude || (this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null),
        departureLatitude: this.departureLatitude || (this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null),
        // 目标地地址和坐标(使用转入医院地址)
        destinationAddress: this.taskForm.hospitalIn.address,
        destinationLongitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null,
        destinationLatitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null,
        // 转运距离和价格(主任务字段)
        distance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null,
        price: this.taskForm.price ? parseFloat(this.taskForm.price) : null,
        // 病情ID列表(用于同步调度单的OrdICD_ID参数)
        diseaseIds: this.selectedDiseases.map(d => d.id).filter(id => id !== null),
        // 执行人员列表
        assignees: this.selectedStaff.map(staff => ({
        // 执行人员列表(确保不为 null)
        assignees: (this.selectedStaff && Array.isArray(this.selectedStaff)) ? this.selectedStaff.map(staff => ({
          userId: staff.userId,
          userName: staff.nickName,
          userType: staff.type
        })),
        emergencyInfo: {
          patientContact: this.taskForm.patient.contact,
          patientPhone: this.taskForm.patient.phone,
          patientName: this.taskForm.patient.name,
          patientGender: this.taskForm.patient.gender,
          patientIdCard: this.taskForm.patient.idCard,
          patientCondition: conditionText, // 使用合并后的病情信息
          hospitalOutName: this.taskForm.hospitalOut.name,
          hospitalOutDepartment: this.taskForm.hospitalOut.department,
          hospitalOutBedNumber: this.taskForm.hospitalOut.bedNumber,
          hospitalOutAddress: this.taskForm.hospitalOut.address,
          // 转出医院GPS坐标(保存到扩展表)
          hospitalOutLongitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null,
          hospitalOutLatitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null,
          hospitalInName: this.taskForm.hospitalIn.name,
          hospitalInDepartment: this.taskForm.hospitalIn.department,
          hospitalInBedNumber: this.taskForm.hospitalIn.bedNumber,
          hospitalInAddress: this.taskForm.hospitalIn.address,
          // 转入医院GPS坐标(保存到扩展表)
          hospitalInLongitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null,
          hospitalInLatitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null,
          transferDistance: this.taskForm.transferDistance ? parseFloat(this.taskForm.transferDistance) : null,
          transferPrice: this.taskForm.price ? parseFloat(this.taskForm.price) : null,
        })) : [],
        // 患者信息(嵌套对象)
        patient: {
          contact: this.taskForm.patient.contact,
          phone: this.taskForm.patient.phone,
          name: this.taskForm.patient.name,
          gender: this.taskForm.patient.gender,
          idCard: this.taskForm.patient.idCard,
          condition: conditionText, // 使用合并后的病情信息
          // 病情详细信息(过滤掉空的病情名称)
          diseases: this.selectedDiseases
            .filter(d => d.icdName && d.icdName.trim())
@@ -958,16 +1050,32 @@
              icdCode: d.icdCode,
              icdName: d.icdName
            }))
        },
        // 转出医院信息(嵌套对象)
        hospitalOut: {
          id: this.taskForm.hospitalOut.id,
          name: this.taskForm.hospitalOut.name,
          address: this.taskForm.hospitalOut.address,
          longitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lng : null,
          latitude: this.addressCoordinates.hospitalOutAddress ? this.addressCoordinates.hospitalOutAddress.lat : null,
          department: this.taskForm.hospitalOut.department,
          departmentId: this.taskForm.hospitalOut.departmentId,
          bedNumber: this.taskForm.hospitalOut.bedNumber
        },
        // 转入医院信息(嵌套对象)
        hospitalIn: {
          id: this.taskForm.hospitalIn.id,
          name: this.taskForm.hospitalIn.name,
          address: this.taskForm.hospitalIn.address,
          longitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lng : null,
          latitude: this.addressCoordinates.hospitalInAddress ? this.addressCoordinates.hospitalInAddress.lat : null,
          department: this.taskForm.hospitalIn.department,
          departmentId: this.taskForm.hospitalIn.departmentId,
          bedNumber: this.taskForm.hospitalIn.bedNumber
        }
      }
      // 出发地GPS坐标
      if (this.departureLongitude && this.departureLatitude) {
        submitData.departureLongitude = this.departureLongitude
        submitData.departureLatitude = this.departureLatitude
      }
      // 目标地GPS坐标由后端自动获取
      
      return submitData
    },
app/pagesTask/edit-welfare.vue
@@ -171,6 +171,13 @@
      selectedVehicleId: null,
      selectedOrganization: '',
      mapSelectorType: '',
      // 扩展 addressCoordinates 支持多种键名
      addressCoordinates: {
        start: null,
        end: null,
        startAddress: null,
        endAddress: null
      },
      taskForm: {
        serviceTime: '',
        passenger: {
@@ -237,16 +244,22 @@
        
        // 设置地址坐标(使用mixin中的方法)
        if (this.taskDetail.departureLongitude && this.taskDetail.departureLatitude) {
          this.setStartLocation({
          const startLocation = {
            lng: this.taskDetail.departureLongitude,
            lat: this.taskDetail.departureLatitude
          })
          }
          this.setStartLocation(startLocation)
          // 同时保存到 startAddress 键
          this.addressCoordinates.startAddress = startLocation
        }
        if (this.taskDetail.destinationLongitude && this.taskDetail.destinationLatitude) {
          this.setEndLocation({
          const endLocation = {
            lng: this.taskDetail.destinationLongitude,
            lat: this.taskDetail.destinationLatitude
          })
          }
          this.setEndLocation(endLocation)
          // 同时保存到 endAddress 键
          this.addressCoordinates.endAddress = endLocation
        }
      }).catch(error => {
        console.error('加载任务详情失败:', error)
@@ -288,10 +301,14 @@
      
      if (this.mapSelectorType === 'startAddress') {
        this.taskForm.startAddress = formattedAddress
        this.setLocationByAddress('start', address)
        const location = this.setLocationByAddress('start', address)
        // 同时保存到 startAddress 键
        this.addressCoordinates.startAddress = location
      } else if (this.mapSelectorType === 'endAddress') {
        this.taskForm.endAddress = formattedAddress
        this.setLocationByAddress('end', address)
        const location = this.setLocationByAddress('end', address)
        // 同时保存到 endAddress 键
        this.addressCoordinates.endAddress = location
      }
      
      // 监听mixin中的距离计算完成事件
@@ -345,29 +362,32 @@
    },
    
    buildSubmitData() {
      // 构建提交数据 - 使用与 TaskCreateVO 一致的结构
      const submitData = {
        taskId: this.taskId,
        taskType: 'WELFARE',
        vehicleIds: this.selectedVehicleId ? [this.selectedVehicleId] : [],
        plannedStartTime: this.taskForm.serviceTime,
        welfareInfo: {
          passengerContact: this.taskForm.passenger.contact,
          passengerPhone: this.taskForm.passenger.phone,
          pickupAddress: this.taskForm.startAddress,
          destinationAddress: this.taskForm.endAddress,
          serviceDistance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null,
          servicePrice: this.taskForm.price ? parseFloat(this.taskForm.price) : null
        // 出发地地址和坐标
        departureAddress: this.taskForm.startAddress,
        departureLongitude: this.addressCoordinates.startAddress ? this.addressCoordinates.startAddress.lng : null,
        departureLatitude: this.addressCoordinates.startAddress ? this.addressCoordinates.startAddress.lat : null,
        // 目标地地址和坐标
        destinationAddress: this.taskForm.endAddress,
        destinationLongitude: this.addressCoordinates.endAddress ? this.addressCoordinates.endAddress.lng : null,
        destinationLatitude: this.addressCoordinates.endAddress ? this.addressCoordinates.endAddress.lat : null,
        // 距离和价格(主任务字段)
        distance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null,
        price: this.taskForm.price ? parseFloat(this.taskForm.price) : null,
        // 乘客信息(嵌套对象)
        passenger: {
          contact: this.taskForm.passenger.contact,
          phone: this.taskForm.passenger.phone
        }
      }
      if (this.addressCoordinates.startAddress) {
        submitData.departureLongitude = this.addressCoordinates.startAddress.lng
        submitData.departureLatitude = this.addressCoordinates.startAddress.lat
      }
      if (this.addressCoordinates.endAddress) {
        submitData.destinationLongitude = this.addressCoordinates.endAddress.lng
        submitData.destinationLatitude = this.addressCoordinates.endAddress.lat
      }
      
      return submitData
app/pagesTask/edit.vue
@@ -150,6 +150,13 @@
      taskId: null,
      taskDetail: null,
      mapSelectorType: '',
      // 扩展 addressCoordinates 支持多种键名
      addressCoordinates: {
        start: null,
        end: null,
        startLocation: null,
        endLocation: null
      },
      taskForm: {
        taskDescription: '',
        taskType: '',
@@ -213,16 +220,22 @@
        
        // 设置地址坐标(使用mixin中的方法)
        if (this.taskDetail.departureLongitude && this.taskDetail.departureLatitude) {
          this.setStartLocation({
          const startLocation = {
            lng: this.taskDetail.departureLongitude,
            lat: this.taskDetail.departureLatitude
          })
          }
          this.setStartLocation(startLocation)
          // 同时保存到 startLocation 键
          this.addressCoordinates.startLocation = startLocation
        }
        if (this.taskDetail.destinationLongitude && this.taskDetail.destinationLatitude) {
          this.setEndLocation({
          const endLocation = {
            lng: this.taskDetail.destinationLongitude,
            lat: this.taskDetail.destinationLatitude
          })
          }
          this.setEndLocation(endLocation)
          // 同时保存到 endLocation 键
          this.addressCoordinates.endLocation = endLocation
        }
      }).catch(error => {
        console.error('加载任务详情失败:', error)
@@ -264,10 +277,14 @@
      
      if (this.mapSelectorType === 'startLocation') {
        this.taskForm.startLocation = formattedAddress
        this.setLocationByAddress('start', address)
        const location = this.setLocationByAddress('start', address)
        // 同时保存到 startLocation 键
        this.addressCoordinates.startLocation = location
      } else if (this.mapSelectorType === 'endLocation') {
        this.taskForm.endLocation = formattedAddress
        this.setLocationByAddress('end', address)
        const location = this.setLocationByAddress('end', address)
        // 同时保存到 endLocation 键
        this.addressCoordinates.endLocation = location
      }
      
      // 监听mixin中的距离计算完成事件
@@ -323,6 +340,7 @@
    },
    
    buildSubmitData() {
      // 构建提交数据 - 使用与 TaskCreateVO 一致的结构
      const submitData = {
        taskId: this.taskId,
        taskDescription: this.taskForm.taskDescription,
@@ -330,20 +348,21 @@
        vehicleIds: this.taskForm.vehicleId ? [this.taskForm.vehicleId] : [],
        plannedStartTime: this.taskForm.plannedStartTime,
        plannedEndTime: this.taskForm.plannedEndTime,
        // 出发地地址和坐标
        departureAddress: this.taskForm.startLocation,
        departureLongitude: this.addressCoordinates.startLocation ? this.addressCoordinates.startLocation.lng : null,
        departureLatitude: this.addressCoordinates.startLocation ? this.addressCoordinates.startLocation.lat : null,
        // 目标地地址和坐标
        destinationAddress: this.taskForm.endLocation,
        estimatedDistance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null,
        destinationLongitude: this.addressCoordinates.endLocation ? this.addressCoordinates.endLocation.lng : null,
        destinationLatitude: this.addressCoordinates.endLocation ? this.addressCoordinates.endLocation.lat : null,
        // 距离(主任务字段)
        distance: this.taskForm.distance ? parseFloat(this.taskForm.distance) : null,
        remark: this.taskForm.remark
      }
      if (this.addressCoordinates.startLocation) {
        submitData.departureLongitude = this.addressCoordinates.startLocation.lng
        submitData.departureLatitude = this.addressCoordinates.startLocation.lat
      }
      if (this.addressCoordinates.endLocation) {
        submitData.destinationLongitude = this.addressCoordinates.endLocation.lng
        submitData.destinationLatitude = this.addressCoordinates.endLocation.lat
      }
      
      return submitData
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -136,7 +136,7 @@
    @Log(title = "任务管理", businessType = BusinessType.UPDATE)
    @PutMapping("/admin")
    public AjaxResult adminEdit(@RequestBody TaskUpdateVO updateVO) {
        return toAjax(sysTaskService.updateSysTask(updateVO));
        return toAjax(sysTaskService.updateSysTask(updateVO,false));
    }
    /**
@@ -145,7 +145,7 @@
    @Log(title = "任务修改", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult appEdit(@RequestBody TaskUpdateVO updateVO) {
        return toAjax(sysTaskService.updateSysTask(updateVO));
        return toAjax(sysTaskService.updateSysTask(updateVO,false));
    }
    /**
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskVehicleController.java
@@ -1,6 +1,8 @@
package com.ruoyi.web.controller.task;
import java.util.List;
import com.ruoyi.common.utils.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -69,7 +71,9 @@
    @PostMapping("/assign/{taskId}")
    public AjaxResult assignVehicle(@PathVariable("taskId") Long taskId, @RequestBody AssignVehicleRequest request) {
        try {
            int result = sysTaskService.assignVehicleToTask(taskId, request.getVehicleId(), request.getRemark());
            Long userId= SecurityUtils.getUserId();
            String username = SecurityUtils.getUsername();
            int result = sysTaskService.assignVehicleToTask(taskId, request.getVehicleId(), request.getRemark(), userId, username);
            if (result > 0) {
                return success("分配成功");
            } else {
@@ -89,7 +93,9 @@
        try {
            // 设置请求对象中的taskId,确保参数一致性
            request.setTaskId(taskId);
            int result = sysTaskService.assignMultipleVehiclesToTask(request.getTaskId(), request.getVehicleIds(), request.getRemark());
            Long userId= SecurityUtils.getUserId();
            String username = SecurityUtils.getUsername();
            int result = sysTaskService.assignMultipleVehiclesToTask(request.getTaskId(), request.getVehicleIds(), request.getRemark(), userId, username);
            if (result > 0) {
                return success("批量分配成功,共分配 " + result + " 辆车");
            } else {
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/LegacySystemSyncTask.java
@@ -228,7 +228,7 @@
     * cron表达式: 0 0/10 * * * ? (每10分钟执行一次)
     */
    public void syncAdditionalFeeToLegacy() {
        log.info("开始执行附加费用同步定时任务(新系统 -> 旧系统)");
        log.info("开始执行新系统附加费用同步定时任务(新系统 -> 旧系统)");
        try {
            int successCount = additionalFeeSyncService.batchSyncAdditionalFeeToLegacy();
            log.info("附加费用同步完成,成功同步: {} 条记录", successCount);
@@ -248,11 +248,11 @@
     * cron表达式: 0 0/15 * * * ? (每15分钟执行一次)
     */
    public void syncAdditionalFeeFromLegacy() {
        log.info("开始执行附加费用同步定时任务(旧系统 -> 新系统)");
        log.info("开始执行旧系统附加费用同步定时任务(旧系统 -> 新系统)");
        try {
            // 同步最近24小时内的记录
            int successCount = additionalFeeSyncService.batchSyncAdditionalFeeFromLegacy(24);
            log.info("附加费用同步完成,成功同步: {} 条记录", successCount);
            int successCount = additionalFeeSyncService.batchSyncAdditionalFeeFromLegacy(72);
            log.info("旧系统附加费用同步完成,成功同步: {} 条记录", successCount);
        } catch (Exception e) {
            log.error("附加费用同步异常", e);
        }
ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/TaskUpdateVO.java
@@ -1,523 +1,24 @@
package com.ruoyi.system.domain.vo;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 任务更新对象
 * 任务更新对象(继承自TaskCreateVO)
 * 
 * @author ruoyi
 * @date 2024-01-15
 */
public class TaskUpdateVO {
@Data
@EqualsAndHashCode(callSuper = true)
public class TaskUpdateVO extends TaskCreateVO {
    
    /** 任务ID */
    /** 任务ID(必填,用于更新指定任务) */
    private Long taskId;
    /** 任务描述 */
    private String taskDescription;
    /** 出发地址 */
    private String departureAddress;
    /** 目的地址 */
    private String destinationAddress;
    /** 计划开始时间 */
    /** 更新时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date plannedStartTime;
    /** 计划结束时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date plannedEndTime;
    /** 执行人ID */
    private Long assigneeId;
    /** 备注 */
    private String remark;
    /** 出发地经度 */
    private BigDecimal departureLongitude;
    /** 出发地纬度 */
    private BigDecimal departureLatitude;
    /** 目的地经度 */
    private BigDecimal destinationLongitude;
    /** 目的地纬度 */
    private BigDecimal destinationLatitude;
    /** 任务类型 */
    private String taskType;
    /** 部门ID */
    private Long deptId;
    /** 车辆ID列表 */
    private List<Long> vehicleIds;
    /** 病情ID列表(用于同步调度单的OrdICD_ID参数) */
    private List<Long> diseaseIds;
    /** 执行人员列表(包含角色类型) */
    private List<AssigneeInfo> assignees;
    /** 急救转运任务扩展信息 */
    private EmergencyInfoVO emergencyInfo;
    public Long getTaskId() {
        return taskId;
    }
    public void setTaskId(Long taskId) {
        this.taskId = taskId;
    }
    public String getTaskDescription() {
        return taskDescription;
    }
    public void setTaskDescription(String taskDescription) {
        this.taskDescription = taskDescription;
    }
    public String getDepartureAddress() {
        return departureAddress;
    }
    public void setDepartureAddress(String departureAddress) {
        this.departureAddress = departureAddress;
    }
    public String getDestinationAddress() {
        return destinationAddress;
    }
    public void setDestinationAddress(String destinationAddress) {
        this.destinationAddress = destinationAddress;
    }
    public Date getPlannedStartTime() {
        return plannedStartTime;
    }
    public void setPlannedStartTime(Date plannedStartTime) {
        this.plannedStartTime = plannedStartTime;
    }
    public Date getPlannedEndTime() {
        return plannedEndTime;
    }
    public void setPlannedEndTime(Date plannedEndTime) {
        this.plannedEndTime = plannedEndTime;
    }
    public Long getAssigneeId() {
        return assigneeId;
    }
    public void setAssigneeId(Long assigneeId) {
        this.assigneeId = assigneeId;
    }
    public String getRemark() {
        return remark;
    }
    public void setRemark(String remark) {
        this.remark = remark;
    }
    public BigDecimal getDepartureLongitude() {
        return departureLongitude;
    }
    public void setDepartureLongitude(BigDecimal departureLongitude) {
        this.departureLongitude = departureLongitude;
    }
    public BigDecimal getDepartureLatitude() {
        return departureLatitude;
    }
    public void setDepartureLatitude(BigDecimal departureLatitude) {
        this.departureLatitude = departureLatitude;
    }
    public BigDecimal getDestinationLongitude() {
        return destinationLongitude;
    }
    public void setDestinationLongitude(BigDecimal destinationLongitude) {
        this.destinationLongitude = destinationLongitude;
    }
    public BigDecimal getDestinationLatitude() {
        return destinationLatitude;
    }
    public void setDestinationLatitude(BigDecimal destinationLatitude) {
        this.destinationLatitude = destinationLatitude;
    }
    public String getTaskType() {
        return taskType;
    }
    public void setTaskType(String taskType) {
        this.taskType = taskType;
    }
    public Long getDeptId() {
        return deptId;
    }
    public void setDeptId(Long deptId) {
        this.deptId = deptId;
    }
    public List<Long> getVehicleIds() {
        return vehicleIds;
    }
    public void setVehicleIds(List<Long> vehicleIds) {
        this.vehicleIds = vehicleIds;
    }
    public List<Long> getDiseaseIds() {
        return diseaseIds;
    }
    public void setDiseaseIds(List<Long> diseaseIds) {
        this.diseaseIds = diseaseIds;
    }
    public EmergencyInfoVO getEmergencyInfo() {
        return emergencyInfo;
    }
    public void setEmergencyInfo(EmergencyInfoVO emergencyInfo) {
        this.emergencyInfo = emergencyInfo;
    }
    public List<AssigneeInfo> getAssignees() {
        return assignees;
    }
    public void setAssignees(List<AssigneeInfo> assignees) {
        this.assignees = assignees;
    }
    /**
     * 急救转运任务扩展信息内部类
     */
    public static class EmergencyInfoVO {
        /** 患者联系人 */
        private String patientContact;
        /** 患者联系电话 */
        private String patientPhone;
        /** 患者姓名 */
        private String patientName;
        /** 患者性别 */
        private String patientGender;
        /** 患者身份证号 */
        private String patientIdCard;
        /** 患者病情描述 */
        private String patientCondition;
        /** 转出医院ID */
        private Long hospitalOutId;
        /** 转出医院名称 */
        private String hospitalOutName;
        /** 转出医院科室 */
        private String hospitalOutDepartment;
        /** 转出医院科室ID */
        private String hospitalOutDepartmentId;
        /** 转出医院床号 */
        private String hospitalOutBedNumber;
        /** 转出医院地址 */
        private String hospitalOutAddress;
        /** 转出医院经度 */
        private BigDecimal hospitalOutLongitude;
        /** 转出医院纬度 */
        private BigDecimal hospitalOutLatitude;
        /** 转入医院ID */
        private Long hospitalInId;
        /** 转入医院名称 */
        private String hospitalInName;
        /** 转入医院科室 */
        private String hospitalInDepartment;
        /** 转入医院科室ID */
        private String hospitalInDepartmentId;
        /** 转入医院床号 */
        private String hospitalInBedNumber;
        /** 转入医院地址 */
        private String hospitalInAddress;
        /** 转入医院经度 */
        private BigDecimal hospitalInLongitude;
        /** 转入医院纬度 */
        private BigDecimal hospitalInLatitude;
        /** 转运公里数 */
        private BigDecimal transferDistance;
        /** 转运费用 */
        private BigDecimal transferPrice;
        // Getters and Setters
        public String getPatientContact() {
            return patientContact;
        }
        public void setPatientContact(String patientContact) {
            this.patientContact = patientContact;
        }
        public String getPatientPhone() {
            return patientPhone;
        }
        public void setPatientPhone(String patientPhone) {
            this.patientPhone = patientPhone;
        }
        public String getPatientName() {
            return patientName;
        }
        public void setPatientName(String patientName) {
            this.patientName = patientName;
        }
        public String getPatientGender() {
            return patientGender;
        }
        public void setPatientGender(String patientGender) {
            this.patientGender = patientGender;
        }
        public String getPatientIdCard() {
            return patientIdCard;
        }
        public void setPatientIdCard(String patientIdCard) {
            this.patientIdCard = patientIdCard;
        }
        public String getPatientCondition() {
            return patientCondition;
        }
        public void setPatientCondition(String patientCondition) {
            this.patientCondition = patientCondition;
        }
        public Long getHospitalOutId() {
            return hospitalOutId;
        }
        public void setHospitalOutId(Long hospitalOutId) {
            this.hospitalOutId = hospitalOutId;
        }
        public String getHospitalOutName() {
            return hospitalOutName;
        }
        public void setHospitalOutName(String hospitalOutName) {
            this.hospitalOutName = hospitalOutName;
        }
        public String getHospitalOutDepartment() {
            return hospitalOutDepartment;
        }
        public void setHospitalOutDepartment(String hospitalOutDepartment) {
            this.hospitalOutDepartment = hospitalOutDepartment;
        }
        public String getHospitalOutDepartmentId() {
            return hospitalOutDepartmentId;
        }
        public void setHospitalOutDepartmentId(String hospitalOutDepartmentId) {
            this.hospitalOutDepartmentId = hospitalOutDepartmentId;
        }
        public String getHospitalOutBedNumber() {
            return hospitalOutBedNumber;
        }
        public void setHospitalOutBedNumber(String hospitalOutBedNumber) {
            this.hospitalOutBedNumber = hospitalOutBedNumber;
        }
        public String getHospitalOutAddress() {
            return hospitalOutAddress;
        }
        public void setHospitalOutAddress(String hospitalOutAddress) {
            this.hospitalOutAddress = hospitalOutAddress;
        }
        public BigDecimal getHospitalOutLongitude() {
            return hospitalOutLongitude;
        }
        public void setHospitalOutLongitude(BigDecimal hospitalOutLongitude) {
            this.hospitalOutLongitude = hospitalOutLongitude;
        }
        public BigDecimal getHospitalOutLatitude() {
            return hospitalOutLatitude;
        }
        public void setHospitalOutLatitude(BigDecimal hospitalOutLatitude) {
            this.hospitalOutLatitude = hospitalOutLatitude;
        }
        public Long getHospitalInId() {
            return hospitalInId;
        }
        public void setHospitalInId(Long hospitalInId) {
            this.hospitalInId = hospitalInId;
        }
        public String getHospitalInName() {
            return hospitalInName;
        }
        public void setHospitalInName(String hospitalInName) {
            this.hospitalInName = hospitalInName;
        }
        public String getHospitalInDepartment() {
            return hospitalInDepartment;
        }
        public void setHospitalInDepartment(String hospitalInDepartment) {
            this.hospitalInDepartment = hospitalInDepartment;
        }
        public String getHospitalInDepartmentId() {
            return hospitalInDepartmentId;
        }
        public void setHospitalInDepartmentId(String hospitalInDepartmentId) {
            this.hospitalInDepartmentId = hospitalInDepartmentId;
        }
        public String getHospitalInBedNumber() {
            return hospitalInBedNumber;
        }
        public void setHospitalInBedNumber(String hospitalInBedNumber) {
            this.hospitalInBedNumber = hospitalInBedNumber;
        }
        public String getHospitalInAddress() {
            return hospitalInAddress;
        }
        public void setHospitalInAddress(String hospitalInAddress) {
            this.hospitalInAddress = hospitalInAddress;
        }
        public BigDecimal getHospitalInLongitude() {
            return hospitalInLongitude;
        }
        public void setHospitalInLongitude(BigDecimal hospitalInLongitude) {
            this.hospitalInLongitude = hospitalInLongitude;
        }
        public BigDecimal getHospitalInLatitude() {
            return hospitalInLatitude;
        }
        public void setHospitalInLatitude(BigDecimal hospitalInLatitude) {
            this.hospitalInLatitude = hospitalInLatitude;
        }
        public BigDecimal getTransferDistance() {
            return transferDistance;
        }
        public void setTransferDistance(BigDecimal transferDistance) {
            this.transferDistance = transferDistance;
        }
        public BigDecimal getTransferPrice() {
            return transferPrice;
        }
        public void setTransferPrice(BigDecimal transferPrice) {
            this.transferPrice = transferPrice;
        }
    }
    /**
     * 执行人员信息内部类
     */
    public static class AssigneeInfo {
        /** 用户ID */
        private Long userId;
        /** 用户姓名 */
        private String userName;
        /** 用户类型(角色):driver-司机, doctor-医生, nurse-护士 */
        private String userType;
        public Long getUserId() {
            return userId;
        }
        public void setUserId(Long userId) {
            this.userId = userId;
        }
        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
        public String getUserType() {
            return userType;
        }
        public void setUserType(String userType) {
            this.userType = userType;
        }
    }
    private Date updateTime;
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IAdditionalFeeSyncService.java
@@ -1,5 +1,8 @@
package com.ruoyi.system.service;
import com.ruoyi.system.domain.PaidMoneyAdd;
import com.ruoyi.system.domain.SysTaskAdditionalFee;
/**
 * 附加费用同步Service接口
 * 
@@ -14,16 +17,16 @@
     * @param feeId 附加费用ID
     * @return 是否同步成功
     */
    boolean syncAdditionalFeeToLegacy(Long feeId);
    boolean syncAdditionalFeeToLegacy(SysTaskAdditionalFee fee);
    
    /**
     * 将旧系统PaidMoney_Add记录同步到新系统
     * 
     * @param paidMoneyAddId 旧系统附加费用记录ID
     * @param paidMoneyAdd 旧系统附加费用记录ID
     * @return 是否同步成功
     */
    boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId);
    boolean syncAdditionalFeeFromLegacy(PaidMoneyAdd paidMoneyAdd);
    /**
     * 批量同步新系统未同步的附加费用到旧系统
     * 
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -2,15 +2,13 @@
import java.util.Date;
import java.util.List;
import com.ruoyi.system.domain.vo.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.SysTaskLog;
import com.ruoyi.system.domain.SysTaskVehicle;
import com.ruoyi.system.domain.SysTaskAttachment;
import com.ruoyi.system.domain.vo.TaskQueryVO;
import com.ruoyi.system.domain.vo.TaskCreateVO;
import com.ruoyi.system.domain.vo.TaskUpdateVO;
import com.ruoyi.system.domain.vo.TaskStatisticsVO;
import com.ruoyi.system.domain.enums.TaskStatus;
/**
@@ -63,7 +61,22 @@
     * @param updateVO 任务更新对象
     * @return 结果
     */
    public int updateSysTask(TaskUpdateVO updateVO);
    public int updateSysTask(TaskUpdateVO updateVO,Boolean updateFromLegacy);
    /**
     * 修改任务管理(允许从外部传入用户信息、部门信息和时间信息) 用于从旧系统中同步过来
     * @param updateVO
     * @param serviceOrderId
     * @param dispatchOrderId
     * @param serviceOrdNo
     * @param userId
     * @param userName
     * @param deptId
     * @param createTime
     * @param updateTime
     * @return
     */
    public int updateTask(TaskUpdateVO updateVO, String serviceOrderId, String dispatchOrderId, String serviceOrdNo, Long userId, String userName, Long deptId, Date createTime, Date updateTime);
    /**
     * 批量删除任务管理
@@ -151,7 +164,7 @@
     * @param remark 备注
     * @return 结果
     */
    public int assignVehicleToTask(Long taskId, Long vehicleId, String remark);
    public int assignVehicleToTask(Long taskId, Long vehicleId, String remark,Long userId,String userName);
    /**
     * 取消任务车辆分配
@@ -170,7 +183,7 @@
     * @param remark 备注
     * @return 结果
     */
    public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark);
    public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark,Long userId,String userName);
    /**
     * 查询任务关联的车辆
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java
@@ -1,6 +1,5 @@
package com.ruoyi.system.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@@ -9,11 +8,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.domain.PaidMoneyAdd;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.SysTaskAdditionalFee;
@@ -92,19 +87,13 @@
    }
    
    @Override
    public boolean syncAdditionalFeeToLegacy(Long feeId) {
    public boolean syncAdditionalFeeToLegacy(SysTaskAdditionalFee fee) {
        Long feeId= fee.getId();
        try {
            // 1. 查询新系统附加费用记录
            SysTaskAdditionalFee fee = additionalFeeMapper.selectByTaskId(null).stream()
                .filter(f -> f.getId().equals(feeId))
                .findFirst()
                .orElse(null);
            
            if (fee == null) {
                log.info("新系统附加费用记录不存在,feeId: {}", feeId);
                return false;
            }
         // 1. 检查是否已同步
            // 2. 如果已同步过,跳过
            if (fee.getPid() != null && fee.getPid() > 0) {
                log.info("附加费用已同步,feeId: {}, pid: {}", feeId, fee.getPid());
@@ -172,44 +161,47 @@
            return false;
        }
    }
    /**
     * 从旧系统同步附加费用
     * @param paidMoneyAdd 旧系统附加费用记录ID
     * @return
     */
    @Override
    public boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId) {
    public boolean syncAdditionalFeeFromLegacy(PaidMoneyAdd paidMoneyAdd) {
        try {
            // 1. 查询旧系统PaidMoney_Add记录
            PaidMoneyAdd paidMoneyAdd = paidMoneyAddMapper.selectById(paidMoneyAddId);
            if (paidMoneyAdd == null) {
                log.error("旧系统附加费用记录不存在,paidMoneyAddId: {}", paidMoneyAddId);
                return false;
            }
            Long paidMoneyAddId = paidMoneyAdd.getId();
            log.info("1.开始同步旧系统附加费用,paidMoneyAddId: {}", paidMoneyAddId);
            // 2. 检查是否已同步过
            SysTaskAdditionalFee existFee = additionalFeeMapper.selectByPid(paidMoneyAddId);
            if (existFee != null) {
                log.info("旧系统附加费用记录已同步,paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, existFee.getId());
                return true;
            }
            log.info("2.开始同步旧系统附加费用,paidMoneyAddId: {}", paidMoneyAddId);
            
            // 3. 根据ServiceOrdID查询新系统任务
            if (paidMoneyAdd.getToServiceOrdID() == null) {
                log.warn("旧系统附加费用记录缺少ServiceOrdID,paidMoneyAddId: {},跳过同步", paidMoneyAddId);
                return false;
            }
            log.info("3.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID());
            
            SysTaskEmergency emergency = sysTaskEmergencyMapper.selectByLegacyServiceOrdId(paidMoneyAdd.getToServiceOrdID());
            if (emergency == null) {
                log.warn("未找到对应的转运任务,ServiceOrdID: {},跳过同步", paidMoneyAdd.getToServiceOrdID());
                log.warn("4.开始同步旧系统附加费用,未找到对应的转运任务,ServiceOrdID: {},跳过同步", paidMoneyAdd.getToServiceOrdID());
                return false;
            }
            log.info("4.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}, DispatchOrdID: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID());
            // 4. 验证DispatchOrdID是否匹配
            if (paidMoneyAdd.getToDispatchOrdID() == null) {
                log.warn("旧系统附加费用记录缺少DispatchOrdID,paidMoneyAddId: {},跳过同步", paidMoneyAddId);
                return false;
            }
            log.info("5.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}, DispatchOrdID: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID());
            if (!paidMoneyAdd.getToDispatchOrdID().equals(emergency.getLegacyDispatchOrdId())) {
                log.warn("转运任务DispatchOrdID不匹配,ServiceOrdID: {}, 附加费用DispatchOrdID: {} vs 任务DispatchOrdID: {},跳过同步",
                log.warn("5.转运任务DispatchOrdID不匹配,ServiceOrdID: {}, 附加费用DispatchOrdID: {} vs 任务DispatchOrdID: {},跳过同步",
                    paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID(), emergency.getLegacyDispatchOrdId());
                return false;
            }
@@ -228,19 +220,19 @@
            fee.setPid(paidMoneyAddId);
            fee.setSyncStatus(2); // 同步成功
            fee.setSyncTime(new Date());
            log.info("6.开始同步旧系统附加费用,paidMoneyAddId: {}, ServiceOrdID: {}, DispatchOrdID: {}, feeId: {}", paidMoneyAddId, paidMoneyAdd.getToServiceOrdID(), paidMoneyAdd.getToDispatchOrdID(), fee.getId());
            // 6. 插入新系统附加费用记录
            int result = additionalFeeMapper.insert(fee);
            if (result > 0) {
                log.info("旧系统附加费用记录同步到新系统成功,paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, fee.getId());
                log.info("6.旧系统附加费用记录同步到新系统成功,paidMoneyAddId: {}, feeId: {}", paidMoneyAddId, fee.getId());
                return true;
            } else {
                log.error("插入新系统附加费用记录失败,paidMoneyAddId: {}", paidMoneyAddId);
                log.error("6.插入新系统附加费用记录失败,paidMoneyAddId: {}", paidMoneyAddId);
                return false;
            }
            
        } catch (Exception e) {
            log.error("同步旧系统附加费用记录到新系统异常,paidMoneyAddId: {}", paidMoneyAddId, e);
            log.error("8.同步旧系统附加费用记录到新系统异常,paidMoneyAddId: {}", paidMoneyAdd.getId(), e);
            return false;
        }
    }
@@ -256,7 +248,7 @@
            
            for (SysTaskAdditionalFee fee : unsyncedFees) {
                try {
                    if (syncAdditionalFeeToLegacy(fee.getId())) {
                    if (syncAdditionalFeeToLegacy(fee)) {
                        successCount++;
                    }
                    // 每条记录间隔1秒,避免过于频繁
@@ -290,7 +282,7 @@
            
            for (PaidMoneyAdd paidMoneyAdd : recentRecords) {
                try {
                    if (syncAdditionalFeeFromLegacy(paidMoneyAdd.getId())) {
                    if (syncAdditionalFeeFromLegacy(paidMoneyAdd)) {
                        successCount++;
                    }
                    // 每条记录间隔1秒,避免过于频繁
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DepartmentSyncServiceImpl.java
@@ -88,11 +88,32 @@
                if (parts.length != 2)
                {
                    log.warn("部门名称格式不正确,跳过: {}", fullName);
                    continue;
                   parts= fullName.split("-");
                   if(parts.length != 2) {
                       continue;
                   }
                }
                String part="";
                //只要发现有(新)或(新)就在branchName中加个新 字
                if(fullName.contains("(新)") || fullName.contains("(新)")) {
                    part="(新)";
                }
                String branchName = parts[0].trim() + "分公司";  // 湛江 -> 湛江分公司
                String deptName = parts[1].trim();              // 护士
                String cityName=parts[0].trim();
                String namePart=parts[0].trim();
                if(namePart.contains("(新)") || namePart.contains("(新)")) {
                    branchName=namePart+"分公司";
                    cityName=namePart;
                    deptName=parts[1].trim();
                }
                else{
                    branchName=namePart+part+"分公司";
                    cityName=namePart+ part;
                    deptName=parts[1].trim();
                }
                // 获取或创建分公司
                Long branchDeptId = branchMap.get(branchName);
@@ -107,7 +128,7 @@
                        branchMap.put(branchName, branchDeptId);
                        
                        // 检查并更新编码
                        syncOrderClassCodes(existingBranch, parts[0].trim(), serviceOrderList, dispatchOrderList,addressList);
                        syncOrderClassCodes(existingBranch, cityName, serviceOrderList, dispatchOrderList,addressList);
//                        existingBranch.setDepartmentId(dto.getDepartmentId());
                        sysDeptMapper.updateDept(existingBranch);
                        log.info("更新分公司编码: {}, 服务单编码: {}, 调度单编码: {}", 
@@ -126,7 +147,7 @@
//                        newBranch.setDepartmentId(dto.getDepartmentId());
                        // 自动匹配并设置服务单和调度单编码
                        syncOrderClassCodes(newBranch, parts[0].trim(), serviceOrderList, dispatchOrderList,addressList);
                        syncOrderClassCodes(newBranch, cityName, serviceOrderList, dispatchOrderList,addressList);
                        sysDeptMapper.insertDept(newBranch);
                        branchDeptId = newBranch.getDeptId();
@@ -479,6 +500,7 @@
        // 遍历编码列表,查找包含城市名称的项
        for (OrderClassDTO dto : orderClassList)
        {
            //有些加了新 TODO
            if (dto.getVtext() != null && dto.getVtext().contains(cityName))
            {
                log.debug("城市名称匹配成功 - 城市: {}, vtext: {}, vOrder2: {}", 
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacySystemSyncServiceImpl.java
@@ -525,7 +525,7 @@
        params.put("ServiceOrdCoName", StringUtils.nvl(emergency.getPatientContact(), ""));
        params.put("ServiceOrdCoPhone", StringUtils.nvl(emergency.getPatientPhone(), ""));
        params.put("ServiceOrdPtName", StringUtils.nvl(emergency.getPatientName(), ""));
        params.put("ServiceOrdTraStreet",StringUtils.nvl(task.getDepartureAddress(), StringUtils.nvl(emergency.getHospitalOutAddress(), "")));
        // 地址信息
        params.put("DispatchOrdTraStreet", StringUtils.nvl(task.getDepartureAddress(), StringUtils.nvl(emergency.getHospitalOutAddress(), "")));
        params.put("DispatchOrdTraEnd", StringUtils.nvl(task.getDestinationAddress(), StringUtils.nvl(emergency.getHospitalInAddress(), "")));
@@ -1188,9 +1188,10 @@
            params.put("DispatchOrdID", emergency.getLegacyDispatchOrdId().toString());
            params.put("ServiceOrdID", emergency.getLegacyServiceOrdId().toString());
            params.put("DispatchOrdState", "3");
            log.info("重新同步调度单到旧系统请求参数: {}", params);
            // 发送HTTP请求到旧系统(使用admin_save_25.asp接口)
            String response = sendHttpPost(legacyConfig.getDispatchUpdateUrl(), params);
            log.info("重新同步调度单到旧系统响应: ServiceOrdID:{},DispatchOrdId:{},Result: {}",emergency.getLegacyServiceOrdId(),emergency.getLegacyDispatchOrdId(), response);
            // 解析响应
            Long dispatchOrdId = parseResponse(response);
            
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/LegacyTransferSyncServiceImpl.java
@@ -1,15 +1,13 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.domain.vo.TaskCreateVO;
import com.ruoyi.system.domain.vo.TaskUpdateVO;
import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
import com.ruoyi.system.mapper.SysTaskMapper;
import com.ruoyi.system.service.ILegacyTransferSyncService;
@@ -19,9 +17,9 @@
import com.ruoyi.system.mapper.VehicleInfoMapper;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.IWechatTaskNotifyService;
import org.apache.poi.ss.usermodel.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -31,7 +29,6 @@
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
/**
 * 旧系统转运单同步Service业务层处理
@@ -67,7 +64,9 @@
    @Autowired
    private IWechatTaskNotifyService wechatTaskNotifyService;
    /**
     * 同步指定日期范围的旧系统转运单到新系统
     * 
@@ -121,6 +120,8 @@
                    // 检查是否已同步
                    if (isTransferOrderSynced(serviceOrdID, dispatchOrdID)) {
                        log.debug("转运单已同步,跳过: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
                        //进行更新操作
                        updateTransferOrder(serviceOrdID, dispatchOrdID, order);
                        continue;
                    }
                    
@@ -194,7 +195,71 @@
            return false;
        }
    }
    private boolean updateTransferOrder(String serviceOrdID, String dispatchOrdID, Map<String, Object> order){
        log.info("开始同步单个转运单: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
        String sysTaskCode="";
        try {
            SysTaskEmergency emergency=sysTaskEmergencyMapper.selectByLegacyServiceOrdId(Long.parseLong(serviceOrdID));
            if(emergency.getNeedResync().equals(1)){
                log.info("新系统需要同步到旧系统那里,所以不要同步旧数据到新系统,serviceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
                return false;
            }
            // 构造TaskCreateVO对象
            TaskCreateVO createTaskVo = buildCreateTaskVo(serviceOrdID, dispatchOrdID, order);
            sysTaskCode = createTaskVo.getTaskCode();
            if (createTaskVo == null) {
                log.error("构造TaskCreateVO失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
                return false;
            }
            // 记录创建的任务信息
            log.debug("准备创建任务: ServiceOrdID={}, DispatchOrdID={}, 患者姓名={}, 转出医院={}, 转入医院={}",
                    serviceOrdID, dispatchOrdID,
                    createTaskVo.getPatient() != null ? createTaskVo.getPatient().getName() : "未知",
                    createTaskVo.getHospitalOut() != null ? createTaskVo.getHospitalOut().getName() : "未知",
                    createTaskVo.getHospitalIn() != null ? createTaskVo.getHospitalIn().getName() : "未知");
            /**
             * 开单时间
             */
            Date ServiceOrd_CC_Time= getDateValue(order, "ServiceOrd_CC_Time");
            // 调用sysTaskService创建任务
            String serviceOrdClass = getStringValue(order,"ServiceOrdClass");
            String serviceOrdNo = getStringValue(order,"ServiceOrdNo");
            Integer oauserId=getIntegerValue(order,"ServiceOrd_NS_ID");
            SysUser sysUser=sysUserService.selectUserByOaUserId(oauserId);
            Long taskCreatorId=sysUser==null?null:sysUser.getUserId();
            String createUserName=sysUser==null?"system":sysUser.getUserName();
            SysDept dept=sysDeptService.selectDeptByServiceClass(serviceOrdClass);
            Long deptId=dept==null?null:dept.getDeptId();
            TaskUpdateVO updateTaskVo = new TaskUpdateVO();
            BeanUtils.copyProperties(createTaskVo, updateTaskVo);
            int result = sysTaskService.updateTask(updateTaskVo,serviceOrdID,dispatchOrdID, serviceOrdNo, taskCreatorId,createUserName, deptId, ServiceOrd_CC_Time, ServiceOrd_CC_Time);
            if (result > 0) {
                log.info("转运单同步成功: ServiceOrdID={}, DispatchOrdID={}, 创建的任务ID={}", serviceOrdID, dispatchOrdID, result);
                try {
                    notifyTransferOrderByWechat((long) result, serviceOrdID, dispatchOrdID, serviceOrdNo, ServiceOrd_CC_Time, dept, order);
                } catch (Exception e) {
                    log.error("转运单同步成功后发送微信通知失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID, e);
                }
                return true;
            } else {
                log.error("转运单同步失败: ServiceOrdID={}, DispatchOrdID={}", serviceOrdID, dispatchOrdID);
                return false;
            }
        } catch (Exception e) {
            log.error("同步单个转运单异常: ServiceOrdID={}, DispatchOrdID={},sysTaskCode:{}", serviceOrdID, dispatchOrdID,sysTaskCode, e);
            return false;
        }
    }
    /**
     * 同步单个旧系统转运单到新系统(带详细信息)
     * 
@@ -414,9 +479,6 @@
            String hospitalOutDeptId = getStringValue(order, "ServiceOrdPtServicesID");
            //转出床位
            String serviceOrdPtServices=getStringValue(order, "ServiceOrdPtServices");
            hospitalOutInfo.setDepartmentId(hospitalOutDeptId);
            if (StringUtils.isNotEmpty(hospitalOutDeptId)) {
                String hospitalOutDeptName = legacyTransferSyncMapper.selectDepartmentNameByDeptID(hospitalOutDeptId);
@@ -424,6 +486,8 @@
                    hospitalOutInfo.setDepartment(hospitalOutDeptName);
                }
            }
            //转出床位
            String serviceOrdPtServices=getStringValue(order, "ServiceOrdPtServices");
            if(serviceOrdPtServices!= null){
                hospitalOutInfo.setBedNumber(serviceOrdPtServices);
            }
@@ -471,6 +535,9 @@
            // 设置执行人信息
            List<TaskCreateVO.AssigneeInfo> assignees = queryAssignees(dispatchOrdID);
            createTaskVo.setAssignees(assignees);
            if(!assignees.isEmpty()){
                createTaskVo.setAssigneeId(assignees.get(0).getUserId());
            }
            
            // 设置车辆信息
            // 车辆ID需要根据DispatchOrdCarID查询获取
@@ -602,7 +669,7 @@
            if (assigneeList != null && !assigneeList.isEmpty()) {
                for (Map<String, Object> assigneeMap : assigneeList) {
                    String entourageOAId = getStringValue(assigneeMap, "EntourageOAId");
                    String entourageState = getStringValue(assigneeMap, "EntourageState");
                    String entourageState = getStringValue(assigneeMap, "EntourageID");
                    
                    if (StringUtils.isNotEmpty(entourageOAId)) {
                        try {
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskPaymentServiceImpl.java
@@ -175,7 +175,7 @@
        
        // 异步同步到旧系统
        try {
            additionalFeeSyncService.syncAdditionalFeeToLegacy(fee.getId());
            additionalFeeSyncService.syncAdditionalFeeToLegacy(fee);
        } catch (Exception e) {
            log.error("同步附加费用到旧系统失败", e);
        }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -8,6 +8,7 @@
import java.net.URL;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.system.domain.vo.*;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.utils.TaskCodeGenerator;
import com.ruoyi.common.config.ImageUrlConfig;
@@ -28,10 +29,6 @@
import com.ruoyi.system.domain.SysTaskEmergency;
import com.ruoyi.system.domain.SysTaskWelfare;
import com.ruoyi.system.domain.SysTaskAssignee;
import com.ruoyi.system.domain.vo.TaskQueryVO;
import com.ruoyi.system.domain.vo.TaskCreateVO;
import com.ruoyi.system.domain.vo.TaskUpdateVO;
import com.ruoyi.system.domain.vo.TaskStatisticsVO;
import com.ruoyi.system.domain.enums.TaskStatus;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.service.ISysTaskService;
@@ -259,11 +256,11 @@
                taskVehicle.setTaskId(task.getTaskId());
                taskVehicle.setVehicleId(vehicleId);
                taskVehicle.setAssignTime(DateUtils.getNowDate());
                taskVehicle.setAssignBy(SecurityUtils.getUsername());
                taskVehicle.setAssignBy(username);
                taskVehicle.setStatus("ASSIGNED");
                taskVehicle.setCreateBy(SecurityUtils.getUsername());
                taskVehicle.setCreateBy(username);
                taskVehicle.setCreateTime(DateUtils.getNowDate());
                taskVehicle.setUpdateBy(SecurityUtils.getUsername());
                taskVehicle.setUpdateBy(username);
                taskVehicle.setUpdateTime(DateUtils.getNowDate());
                
                sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle);
@@ -549,49 +546,70 @@
     */
    @Override
    @Transactional
    public int updateSysTask(TaskUpdateVO updateVO) {
    public int updateSysTask(TaskUpdateVO updateVO, Boolean updateFromLegacy) {
        SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(updateVO.getTaskId());
        if (oldTask == null) {
            throw new RuntimeException("任务不存在");
        }
        String userName = SecurityUtils.getUsername();
        
        SysTask task = new SysTask();
        task.setTaskId(updateVO.getTaskId());
        task.setTaskDescription(updateVO.getTaskDescription());
        task.setPlannedStartTime(updateVO.getPlannedStartTime());
        task.setPlannedEndTime(updateVO.getPlannedEndTime());
        task.setAssigneeId(updateVO.getAssigneeId());
        task.setUpdateBy(userName);
        task.setUpdateTime(updateVO.getUpdateTime() != null ? updateVO.getUpdateTime() : DateUtils.getNowDate());
        task.setRemark(updateVO.getRemark());
        // 设置通用地址和坐标信息
        task.setDepartureAddress(updateVO.getDepartureAddress());
        task.setDestinationAddress(updateVO.getDestinationAddress());
        task.setDepartureLongitude(updateVO.getDepartureLongitude());
        task.setDepartureLatitude(updateVO.getDepartureLatitude());
        task.setDestinationLongitude(updateVO.getDestinationLongitude());
        task.setDestinationLatitude(updateVO.getDestinationLatitude());
        task.setPlannedStartTime(updateVO.getPlannedStartTime());
        task.setPlannedEndTime(updateVO.getPlannedEndTime());
        task.setAssigneeId(updateVO.getAssigneeId());
        task.setUpdateBy(SecurityUtils.getUsername());
        task.setUpdateTime(DateUtils.getNowDate());
        task.setRemark(updateVO.getRemark());
        // 设置预计距离
        if (updateVO.getEstimatedDistance() != null) {
            task.setEstimatedDistance(updateVO.getEstimatedDistance());
        } else if (updateVO.getTransferDistance() != null) {
            // 兼容急救转运字段
            task.setEstimatedDistance(updateVO.getTransferDistance());
        } else if (updateVO.getDistance() != null) {
            // 兼容福祉车字段
            task.setEstimatedDistance(updateVO.getDistance());
        }
        
        // 如果更新了部门ID
        if (updateVO.getDeptId() != null) {
            task.setDeptId(updateVO.getDeptId());
        }
        
        // 如果更新了任务编号
        if (updateVO.getTaskCode() != null) {
            task.setTaskCode(updateVO.getTaskCode());
        }
        // 自动获取出发地GPS坐标(如果更新了地址但缺失坐标)
        if (updateVO.getDepartureAddress() != null && 
            (updateVO.getDepartureLongitude() == null || updateVO.getDepartureLatitude() == null) && 
            mapService != null) {
            try {
                Map<String, Double> coords = mapService.geocoding(
                    updateVO.getDepartureAddress(),
                    extractCityFromAddress(updateVO.getDepartureAddress())
                );
                if (coords != null) {
                    task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng")));
                    task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat")));
                    log.info("出发地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
            if (!updateVO.getDepartureAddress().equals(oldTask.getDepartureAddress())) {
                try {
                    Map<String, Double> coords = mapService.geocoding(
                        updateVO.getDepartureAddress(),
                        extractCityFromAddress(updateVO.getDepartureAddress())
                    );
                    if (coords != null) {
                        task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng")));
                        task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat")));
                        log.info("出发地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
                    }
                } catch (Exception e) {
                    log.error("自动获取出发地GPS坐标失败", e);
                }
            } catch (Exception e) {
                log.error("自动获取出发地GPS坐标失败", e);
            }
        }
        
@@ -599,23 +617,22 @@
        if (updateVO.getDestinationAddress() != null && 
            (updateVO.getDestinationLongitude() == null || updateVO.getDestinationLatitude() == null) && 
            mapService != null) {
            try {
                Map<String, Double> coords = mapService.geocoding(
                    updateVO.getDestinationAddress(),
                    extractCityFromAddress(updateVO.getDestinationAddress())
                );
                if (coords != null) {
                    task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng")));
                    task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat")));
                    log.info("目的地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
            if (!updateVO.getDestinationAddress().equals(oldTask.getDestinationAddress())) {
                try {
                    Map<String, Double> coords = mapService.geocoding(
                        updateVO.getDestinationAddress(),
                        extractCityFromAddress(updateVO.getDestinationAddress())
                    );
                    if (coords != null) {
                        task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng")));
                        task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat")));
                        log.info("目的地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
                    }
                } catch (Exception e) {
                    log.error("自动获取目的地GPS坐标失败", e);
                }
            } catch (Exception e) {
                log.error("自动获取目的地GPS坐标失败", e);
            }
        }
        // 重新计算预计公里数
        calculateEstimatedDistance(task);
        
        int result = sysTaskMapper.updateSysTask(task);
        
@@ -640,7 +657,7 @@
                
                // 添加新的车辆关联
                Date now = DateUtils.getNowDate();
                String currentUser = SecurityUtils.getUsername();
                String currentUser = userName;
                for (Long vehicleId : updateVO.getVehicleIds()) {
                    SysTaskVehicle taskVehicle = new SysTaskVehicle();
                    taskVehicle.setTaskId(updateVO.getTaskId());
@@ -665,7 +682,7 @@
                .collect(Collectors.toList());
            
            List<Long> newAssigneeIds = updateVO.getAssignees().stream()
                .map(TaskUpdateVO.AssigneeInfo::getUserId)
                .map(TaskCreateVO.AssigneeInfo::getUserId)
                .collect(Collectors.toList());
            
            // 比较新旧执行人员ID列表,判断是否有变化
@@ -678,17 +695,7 @@
                
                // 添加新的执行人员关联
                if (!updateVO.getAssignees().isEmpty()) {
                    // 将 TaskUpdateVO.AssigneeInfo 转换为 TaskCreateVO.AssigneeInfo
                    List<TaskCreateVO.AssigneeInfo> createAssignees = updateVO.getAssignees().stream()
                        .map(assignee -> {
                            TaskCreateVO.AssigneeInfo createAssignee = new TaskCreateVO.AssigneeInfo();
                            createAssignee.setUserId(assignee.getUserId());
                            createAssignee.setUserName(assignee.getUserName());
                            createAssignee.setUserType(assignee.getUserType());
                            return createAssignee;
                        })
                        .collect(Collectors.toList());
                    saveTaskAssignees(updateVO.getTaskId(), createAssignees, SecurityUtils.getUsername());
                    saveTaskAssignees(updateVO.getTaskId(), updateVO.getAssignees(), userName);
                }
                
                // 标记需要重新同步(人员变更)
@@ -697,37 +704,41 @@
        }
        
        // 更新急救转运扩展信息(检测地址和成交价变更)
        if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && updateVO.getEmergencyInfo() != null) {
        if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType())) {
            // 获取旧的急救转运信息
            SysTaskEmergency oldEmergency = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(updateVO.getTaskId());
            
            // 检测转出医院地址变更
            boolean hospitalOutAddressChanged = false;
            if (updateVO.getEmergencyInfo().getHospitalOutAddress() != null
                && oldEmergency != null
                && !updateVO.getEmergencyInfo().getHospitalOutAddress().equals(oldEmergency.getHospitalOutAddress())) {
            if (updateVO.getHospitalOut() != null && updateVO.getHospitalOut().getAddress() != null
                && oldEmergency != null
                && !updateVO.getHospitalOut().getAddress().equals(oldEmergency.getHospitalOutAddress())) {
                hospitalOutAddressChanged = true;
            }
            
            // 检测转入医院地址变更
            boolean hospitalInAddressChanged = false;
            if (updateVO.getEmergencyInfo().getHospitalInAddress() != null
                && oldEmergency != null
                && !updateVO.getEmergencyInfo().getHospitalInAddress().equals(oldEmergency.getHospitalInAddress())) {
            if (updateVO.getHospitalIn() != null && updateVO.getHospitalIn().getAddress() != null
                && oldEmergency != null
                && !updateVO.getHospitalIn().getAddress().equals(oldEmergency.getHospitalInAddress())) {
                hospitalInAddressChanged = true;
            }
            
            // 检测成交价变更
            boolean transferPriceChanged = false;
            if (updateVO.getEmergencyInfo().getTransferPrice() != null
                && oldEmergency != null
            if (updateVO.getPrice() != null
                && oldEmergency != null
                && oldEmergency.getTransferPrice() != null
                && updateVO.getEmergencyInfo().getTransferPrice().compareTo(oldEmergency.getTransferPrice()) != 0) {
                && updateVO.getPrice().compareTo(oldEmergency.getTransferPrice()) != 0) {
                transferPriceChanged = true;
            }
            ;
            
            // 更新急救转运信息
            updateEmergencyInfo(updateVO.getTaskId(), updateVO);
            if (updateVO.getHospitalOut() != null || updateVO.getHospitalIn() != null || updateVO.getPatient() != null) {
                updateEmergencyInfoFromCreateVO(updateVO.getTaskId(), updateVO, userName);
            }
            
            // 如果地址或成交价发生变更,标记需要重新同步
            if (hospitalOutAddressChanged || hospitalInAddressChanged || transferPriceChanged) {
@@ -735,8 +746,15 @@
            }
        }
        
        // 更新福祉车扩展信息
        if (result > 0 && "WELFARE".equals(oldTask.getTaskType())) {
            if (updateVO.getPassenger() != null || updateVO.getStartAddress() != null || updateVO.getEndAddress() != null) {
                updateWelfareInfoFromCreateVO(updateVO.getTaskId(), updateVO, userName);
            }
        }
        // 如果是急救转运任务且有变更,标记需要重新同步
        if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && needResync) {
        if (result > 0 && "EMERGENCY_TRANSFER".equals(oldTask.getTaskType()) && needResync && !updateFromLegacy) {
            try {
                sysTaskEmergencyService.markNeedResync(updateVO.getTaskId());
            } catch (Exception e) {
@@ -746,9 +764,183 @@
        
        // 记录操作日志
        if (result > 0) {
            recordTaskLog(updateVO.getTaskId(), "UPDATE", "更新任务",
                         buildTaskDescription(oldTask), buildTaskDescription(task),
                         SecurityUtils.getUserId(), SecurityUtils.getUsername());
            recordTaskLog(updateVO.getTaskId(), "UPDATE", "更新任务",
                buildTaskDescription(oldTask), buildTaskDescription(task),
                SecurityUtils.getUserId(), userName);
        }
        return result;
    }
    /**
     * 更新任务(用于旧系统同步)
     *
     * @param updateVO 任务更新对象
     * @param serviceOrderId 旧系统服务单ID
     * @param dispatchOrderId 旧系统调度单ID
     * @param serviceOrdNo 旧系统服务单编号
     * @param userId 用户ID
     * @param userName 用户名
     * @param deptId 部门ID
     * @param createTime 创建时间
     * @param updateTime 更新时间
     * @return 结果
     */
    @Override
    public int updateTask(TaskUpdateVO updateVO, String serviceOrderId, String dispatchOrderId, String serviceOrdNo,
                         Long userId, String userName, Long deptId, Date createTime, Date updateTime) {
        // 通过旧系统服务单ID查找任务
        SysTaskEmergency taskEmergency = sysTaskEmergencyMapper.selectByLegacyServiceOrdId(Long.parseLong(serviceOrderId));
        Long taskId = taskEmergency.getTaskId();
        updateVO.setTaskId(taskId);
        SysTask task = new SysTask();
        task.setTaskId(taskId);
        task.setTaskDescription(updateVO.getTaskDescription());
        task.setPlannedStartTime(updateVO.getPlannedStartTime());
        task.setPlannedEndTime(updateVO.getPlannedEndTime());
        task.setAssigneeId(updateVO.getAssigneeId());
        task.setUpdateBy(userName);
        task.setUpdateTime(DateUtils.getNowDate());
        task.setRemark(updateVO.getRemark());
        // 设置地址和坐标信息
        task.setDepartureAddress(updateVO.getDepartureAddress());
        task.setDestinationAddress(updateVO.getDestinationAddress());
        task.setDepartureLongitude(updateVO.getDepartureLongitude());
        task.setDepartureLatitude(updateVO.getDepartureLatitude());
        task.setDestinationLongitude(updateVO.getDestinationLongitude());
        task.setDestinationLatitude(updateVO.getDestinationLatitude());
        // 如果更新了部门ID
        if (updateVO.getDeptId() != null) {
            task.setDeptId(updateVO.getDeptId());
        }
        // 如果更新了任务编号
        if (updateVO.getTaskCode() != null) {
            task.setTaskCode(updateVO.getTaskCode());
        }
        // 获取旧任务信息,用于判断地址是否变更
        SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(taskId);
        // 自动获取出发地GPS坐标(如果地址变更且缺失坐标)
        if (oldTask != null && updateVO.getDepartureAddress() != null
            && !updateVO.getDepartureAddress().equals(oldTask.getDepartureAddress())
            && (updateVO.getDepartureLongitude() == null || updateVO.getDepartureLatitude() == null)
            && mapService != null) {
            try {
                Map<String, Double> coords = mapService.geocoding(
                    updateVO.getDepartureAddress(),
                    extractCityFromAddress(updateVO.getDepartureAddress())
                );
                if (coords != null) {
                    task.setDepartureLongitude(BigDecimal.valueOf(coords.get("lng")));
                    task.setDepartureLatitude(BigDecimal.valueOf(coords.get("lat")));
                    log.info("出发地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
                }
            } catch (Exception e) {
                log.error("自动获取出发地GPS坐标失败", e);
            }
        }
        // 自动获取目的地GPS坐标(如果地址变更且缺失坐标)
        if (oldTask != null && updateVO.getDestinationAddress() != null
            && !updateVO.getDestinationAddress().equals(oldTask.getDestinationAddress())
            && (updateVO.getDestinationLongitude() == null || updateVO.getDestinationLatitude() == null)
            && mapService != null) {
            try {
                Map<String, Double> coords = mapService.geocoding(
                    updateVO.getDestinationAddress(),
                    extractCityFromAddress(updateVO.getDestinationAddress())
                );
                if (coords != null) {
                    task.setDestinationLongitude(BigDecimal.valueOf(coords.get("lng")));
                    task.setDestinationLatitude(BigDecimal.valueOf(coords.get("lat")));
                    log.info("目的地GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
                }
            } catch (Exception e) {
                log.error("自动获取目的地GPS坐标失败", e);
            }
        }
        int result = sysTaskMapper.updateSysTask(task);
        // 更新车辆关联
        if (result > 0 && updateVO.getVehicleIds() != null && !updateVO.getVehicleIds().isEmpty()) {
            // 查询现有的车辆关联
            List<SysTaskVehicle> existingVehicles = sysTaskVehicleMapper.selectSysTaskVehicleByTaskId(taskId);
            List<Long> existingVehicleIds = existingVehicles.stream()
                .map(SysTaskVehicle::getVehicleId)
                .collect(Collectors.toList());
            // 比较新旧车辆ID列表,判断是否有变化
            boolean vehiclesChanged = !new HashSet<>(existingVehicleIds).equals(new HashSet<>(updateVO.getVehicleIds()));
            // 只有车辆发生变化时才更新
            if (vehiclesChanged) {
                // 删除旧的车辆关联
                sysTaskVehicleMapper.deleteSysTaskVehicleByTaskId(taskId);
                // 添加新的车辆关联
                Date now = DateUtils.getNowDate();
                for (Long vehicleId : updateVO.getVehicleIds()) {
                    SysTaskVehicle taskVehicle = new SysTaskVehicle();
                    taskVehicle.setTaskId(taskId);
                    taskVehicle.setVehicleId(vehicleId);
                    taskVehicle.setAssignTime(now);
                    taskVehicle.setAssignBy(userName);
                    taskVehicle.setCreateTime(now);
                    sysTaskVehicleMapper.insertSysTaskVehicle(taskVehicle);
                }
            }
        }
        // 更新执行人员(检测人员变更)
        if (result > 0 && updateVO.getAssignees() != null) {
            // 查询现有的执行人员
            List<SysTaskAssignee> existingAssignees = sysTaskAssigneeMapper.selectSysTaskAssigneeByTaskId(taskId);
            List<Long> existingAssigneeIds = existingAssignees.stream()
                .map(SysTaskAssignee::getUserId)
                .collect(Collectors.toList());
            List<Long> newAssigneeIds = updateVO.getAssignees().stream()
                .map(TaskCreateVO.AssigneeInfo::getUserId)
                .collect(Collectors.toList());
            // 比较新旧执行人员ID列表,判断是否有变化
            boolean assigneesChanged = !new HashSet<>(existingAssigneeIds).equals(new HashSet<>(newAssigneeIds));
            // 只有执行人员发生变化时才更新
            if (assigneesChanged) {
                // 删除旧的执行人员关联
                sysTaskAssigneeMapper.deleteSysTaskAssigneeByTaskId(taskId);
                // 添加新的执行人员关联
                if (!updateVO.getAssignees().isEmpty()) {
                    saveTaskAssignees(taskId, updateVO.getAssignees(), userName);
                }
            }
        }
        // 更新急救转运扩展信息
        if (result > 0) {
            // 更新旧系统ID
            if (serviceOrderId != null) {
                taskEmergency.setLegacyServiceOrdId(Long.parseLong(serviceOrderId));
            }
            if (dispatchOrderId != null) {
                taskEmergency.setLegacyDispatchOrdId(Long.parseLong(dispatchOrderId));
            }
            if (serviceOrdNo != null) {
                taskEmergency.setLegacyServiceOrdNo(serviceOrdNo);
            }
            // 使用TaskCreateVO的字段来更新急救转运信息
            if (updateVO.getHospitalOut() != null || updateVO.getHospitalIn() != null || updateVO.getPatient() != null) {
                updateEmergencyInfoFromCreateVO(taskId, updateVO, userName);
            }
        }
        
        return result;
@@ -1165,7 +1357,7 @@
     */
    @Override
    @Transactional
    public int assignVehicleToTask(Long taskId, Long vehicleId, String remark) {
    public int assignVehicleToTask(Long taskId, Long vehicleId, String remark,Long userId,String userName) {
        // 检查是否已经分配
        int exists = sysTaskVehicleMapper.checkTaskVehicleExists(taskId, vehicleId);
        if (exists > 0) {
@@ -1176,7 +1368,7 @@
        taskVehicle.setTaskId(taskId);
        taskVehicle.setVehicleId(vehicleId);
        taskVehicle.setAssignTime(DateUtils.getNowDate());
        taskVehicle.setAssignBy(SecurityUtils.getUsername());
        taskVehicle.setAssignBy(userName);
        taskVehicle.setStatus("ASSIGNED");
        taskVehicle.setRemark(remark);
        
@@ -1186,7 +1378,7 @@
        if (result > 0) {
            recordTaskLog(taskId, "ASSIGN", "分配车辆", null, 
                         "分配车辆ID:" + vehicleId + ",备注:" + remark, 
                         SecurityUtils.getUserId(), SecurityUtils.getUsername());
                         userId, userName);
        }
        
        return result;
@@ -1224,10 +1416,10 @@
     */
    @Override
    @Transactional
    public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark) {
    public int assignMultipleVehiclesToTask(Long taskId, List<Long> vehicleIds, String remark,Long userId,String userName) {
        List<SysTaskVehicle> taskVehicles = new ArrayList<>();
        Date now = DateUtils.getNowDate();
        String assignBy = SecurityUtils.getUsername();
        String assignBy = userName;
        
        for (Long vehicleId : vehicleIds) {
            // 检查是否已经分配
@@ -1253,7 +1445,7 @@
        if (result > 0) {
            recordTaskLog(taskId, "ASSIGN", "批量分配车辆", null, 
                         "分配车辆数量:" + result + ",备注:" + remark, 
                         SecurityUtils.getUserId(), SecurityUtils.getUsername());
                         userId,userName);
        }
        
        return result;
@@ -1768,12 +1960,13 @@
    }
    /**
     * 更新急救转运任务扩展信息
     * 从 TaskCreateVO 更新急救转运任务扩展信息
     * 
     * @param taskId 任务ID
     * @param updateVO 任务更新对象
     * @param createVO 任务创建/更新对象
     * @param userName 操作人名
     */
    private void updateEmergencyInfo(Long taskId, TaskUpdateVO updateVO) {
    private void updateEmergencyInfoFromCreateVO(Long taskId, TaskCreateVO createVO, String userName) {
        // 查询现有的扩展信息
        SysTaskEmergency existingInfo = sysTaskEmergencyMapper.selectSysTaskEmergencyByTaskId(taskId);
        if (existingInfo == null) {
@@ -1781,143 +1974,223 @@
            existingInfo = new SysTaskEmergency();
            existingInfo.setTaskId(taskId);
            existingInfo.setCreateTime(DateUtils.getNowDate());
            existingInfo.setCreateBy(SecurityUtils.getUsername());
            existingInfo.setCreateBy(userName);
        }
        TaskUpdateVO.EmergencyInfoVO emergencyInfo = updateVO.getEmergencyInfo();
        
        // 更新患者信息
        if (emergencyInfo.getPatientContact() != null) {
            existingInfo.setPatientContact(emergencyInfo.getPatientContact());
        }
        if (emergencyInfo.getPatientPhone() != null) {
            existingInfo.setPatientPhone(emergencyInfo.getPatientPhone());
        }
        if (emergencyInfo.getPatientName() != null) {
            existingInfo.setPatientName(emergencyInfo.getPatientName());
        }
        if (emergencyInfo.getPatientGender() != null) {
            existingInfo.setPatientGender(emergencyInfo.getPatientGender());
        }
        if (emergencyInfo.getPatientIdCard() != null) {
            existingInfo.setPatientIdCard(emergencyInfo.getPatientIdCard());
        }
        if (emergencyInfo.getPatientCondition() != null) {
            existingInfo.setPatientCondition(emergencyInfo.getPatientCondition());
        if (createVO.getPatient() != null) {
            if (createVO.getPatient().getContact() != null) {
                existingInfo.setPatientContact(createVO.getPatient().getContact());
            }
            if (createVO.getPatient().getPhone() != null) {
                existingInfo.setPatientPhone(createVO.getPatient().getPhone());
            }
            if (createVO.getPatient().getName() != null) {
                existingInfo.setPatientName(createVO.getPatient().getName());
            }
            if (createVO.getPatient().getGender() != null) {
                existingInfo.setPatientGender(createVO.getPatient().getGender());
            }
            if (createVO.getPatient().getIdCard() != null) {
                existingInfo.setPatientIdCard(createVO.getPatient().getIdCard());
            }
            if (createVO.getPatient().getCondition() != null) {
                existingInfo.setPatientCondition(createVO.getPatient().getCondition());
            }
        }
        
        // 更新转出医院信息
        if (emergencyInfo.getHospitalOutId() != null) {
            existingInfo.setHospitalOutId(emergencyInfo.getHospitalOutId());
        }
        if (emergencyInfo.getHospitalOutName() != null) {
            existingInfo.setHospitalOutName(emergencyInfo.getHospitalOutName());
        }
        if (emergencyInfo.getHospitalOutDepartment() != null) {
            existingInfo.setHospitalOutDepartment(emergencyInfo.getHospitalOutDepartment());
        }
        if (emergencyInfo.getHospitalOutDepartmentId() != null) {
            existingInfo.setHospitalOutDepartmentId(emergencyInfo.getHospitalOutDepartmentId());
        }
        if (emergencyInfo.getHospitalOutBedNumber() != null) {
            existingInfo.setHospitalOutBedNumber(emergencyInfo.getHospitalOutBedNumber());
        }
        if (emergencyInfo.getHospitalOutAddress() != null) {
            existingInfo.setHospitalOutAddress(emergencyInfo.getHospitalOutAddress());
            // 如果更新了地址但没有GPS坐标,后端自动获取
            if (emergencyInfo.getHospitalOutLongitude() == null && emergencyInfo.getHospitalOutLatitude() == null && mapService != null) {
                try {
                    Map<String, Double> coords = mapService.geocoding(
                        emergencyInfo.getHospitalOutAddress(),
                        extractCityFromAddress(emergencyInfo.getHospitalOutAddress())
                    );
                    if (coords != null) {
                        existingInfo.setHospitalOutLongitude(BigDecimal.valueOf(coords.get("lng")));
                        existingInfo.setHospitalOutLatitude(BigDecimal.valueOf(coords.get("lat")));
                        log.info("转出医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
        if (createVO.getHospitalOut() != null) {
            if (createVO.getHospitalOut().getId() != null) {
                existingInfo.setHospitalOutId(createVO.getHospitalOut().getId());
            }
            if (createVO.getHospitalOut().getName() != null) {
                existingInfo.setHospitalOutName(createVO.getHospitalOut().getName());
            }
            if (createVO.getHospitalOut().getDepartment() != null) {
                existingInfo.setHospitalOutDepartment(createVO.getHospitalOut().getDepartment());
            }
            if (createVO.getHospitalOut().getDepartmentId() != null) {
                existingInfo.setHospitalOutDepartmentId(createVO.getHospitalOut().getDepartmentId());
            }
            if (createVO.getHospitalOut().getBedNumber() != null) {
                existingInfo.setHospitalOutBedNumber(createVO.getHospitalOut().getBedNumber());
            }
            if (createVO.getHospitalOut().getAddress() != null && !createVO.getHospitalOut().getAddress().equals(existingInfo.getHospitalOutAddress())) {
                existingInfo.setHospitalOutAddress(createVO.getHospitalOut().getAddress());
                // 如枟更新了地址但没有GPS坐标,后端自动获取
                if (createVO.getHospitalOut().getLongitude() == null && createVO.getHospitalOut().getLatitude() == null && mapService != null) {
                    try {
                        Map<String, Double> coords = mapService.geocoding(
                            createVO.getHospitalOut().getAddress(),
                            extractCityFromAddress(createVO.getHospitalOut().getAddress())
                        );
                        if (coords != null) {
                            existingInfo.setHospitalOutLongitude(BigDecimal.valueOf(coords.get("lng")));
                            existingInfo.setHospitalOutLatitude(BigDecimal.valueOf(coords.get("lat")));
                            log.info("转出医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
                        }
                    } catch (Exception e) {
                        log.error("自动获取转出医院GPS坐标失败", e);
                    }
                } catch (Exception e) {
                    log.error("自动获取转出医院GPS坐标失败", e);
                }
            }
        }
        if (emergencyInfo.getHospitalOutLongitude() != null) {
            existingInfo.setHospitalOutLongitude(emergencyInfo.getHospitalOutLongitude());
        }
        if (emergencyInfo.getHospitalOutLatitude() != null) {
            existingInfo.setHospitalOutLatitude(emergencyInfo.getHospitalOutLatitude());
            if (createVO.getHospitalOut().getLongitude() != null) {
                existingInfo.setHospitalOutLongitude(createVO.getHospitalOut().getLongitude());
            }
            if (createVO.getHospitalOut().getLatitude() != null) {
                existingInfo.setHospitalOutLatitude(createVO.getHospitalOut().getLatitude());
            }
        }
        
        // 更新转入医院信息
        if (emergencyInfo.getHospitalInId() != null) {
            existingInfo.setHospitalInId(emergencyInfo.getHospitalInId());
        }
        if (emergencyInfo.getHospitalInName() != null) {
            existingInfo.setHospitalInName(emergencyInfo.getHospitalInName());
        }
        if (emergencyInfo.getHospitalInDepartment() != null) {
            existingInfo.setHospitalInDepartment(emergencyInfo.getHospitalInDepartment());
        }
        if (emergencyInfo.getHospitalInDepartmentId() != null) {
            existingInfo.setHospitalInDepartmentId(emergencyInfo.getHospitalInDepartmentId());
        }
        if (emergencyInfo.getHospitalInBedNumber() != null) {
            existingInfo.setHospitalInBedNumber(emergencyInfo.getHospitalInBedNumber());
        }
        if (emergencyInfo.getHospitalInAddress() != null) {
            existingInfo.setHospitalInAddress(emergencyInfo.getHospitalInAddress());
            // 如果更新了地址但没有GPS坐标,后端自动获取
            if (emergencyInfo.getHospitalInLongitude() == null && emergencyInfo.getHospitalInLatitude() == null && mapService != null) {
                try {
                    Map<String, Double> coords = mapService.geocoding(
                        emergencyInfo.getHospitalInAddress(),
                        extractCityFromAddress(emergencyInfo.getHospitalInAddress())
                    );
                    if (coords != null) {
                        existingInfo.setHospitalInLongitude(BigDecimal.valueOf(coords.get("lng")));
                        existingInfo.setHospitalInLatitude(BigDecimal.valueOf(coords.get("lat")));
                        log.info("转入医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
        if (createVO.getHospitalIn() != null) {
            if (createVO.getHospitalIn().getId() != null) {
                existingInfo.setHospitalInId(createVO.getHospitalIn().getId());
            }
            if (createVO.getHospitalIn().getName() != null) {
                existingInfo.setHospitalInName(createVO.getHospitalIn().getName());
            }
            if (createVO.getHospitalIn().getDepartment() != null) {
                existingInfo.setHospitalInDepartment(createVO.getHospitalIn().getDepartment());
            }
            if (createVO.getHospitalIn().getDepartmentId() != null) {
                existingInfo.setHospitalInDepartmentId(createVO.getHospitalIn().getDepartmentId());
            }
            if (createVO.getHospitalIn().getBedNumber() != null) {
                existingInfo.setHospitalInBedNumber(createVO.getHospitalIn().getBedNumber());
            }
            if (createVO.getHospitalIn().getAddress() != null && !createVO.getHospitalIn().getAddress().equals(existingInfo.getHospitalInAddress())) {
                existingInfo.setHospitalInAddress(createVO.getHospitalIn().getAddress());
                // 如果更新了地址但没有GPS坐标,后端自动获取
                if (createVO.getHospitalIn().getLongitude() == null && createVO.getHospitalIn().getLatitude() == null && mapService != null) {
                    try {
                        Map<String, Double> coords = mapService.geocoding(
                            createVO.getHospitalIn().getAddress(),
                            extractCityFromAddress(createVO.getHospitalIn().getAddress())
                        );
                        if (coords != null) {
                            existingInfo.setHospitalInLongitude(BigDecimal.valueOf(coords.get("lng")));
                            existingInfo.setHospitalInLatitude(BigDecimal.valueOf(coords.get("lat")));
                            log.info("转入医院GPS坐标自动获取成功: {}, {}", coords.get("lng"), coords.get("lat"));
                        }
                    } catch (Exception e) {
                        log.error("自动获取转入医院GPS坐标失败", e);
                    }
                } catch (Exception e) {
                    log.error("自动获取转入医院GPS坐标失败", e);
                }
            }
        }
        if (emergencyInfo.getHospitalInLongitude() != null) {
            existingInfo.setHospitalInLongitude(emergencyInfo.getHospitalInLongitude());
        }
        if (emergencyInfo.getHospitalInLatitude() != null) {
            existingInfo.setHospitalInLatitude(emergencyInfo.getHospitalInLatitude());
            if (createVO.getHospitalIn().getLongitude() != null) {
                existingInfo.setHospitalInLongitude(createVO.getHospitalIn().getLongitude());
            }
            if (createVO.getHospitalIn().getLatitude() != null) {
                existingInfo.setHospitalInLatitude(createVO.getHospitalIn().getLatitude());
            }
        }
        
        // 更新费用信息
        if (emergencyInfo.getTransferDistance() != null) {
            existingInfo.setTransferDistance(emergencyInfo.getTransferDistance());
        if (createVO.getTransferDistance() != null) {
            existingInfo.setTransferDistance(createVO.getTransferDistance());
        }
        if (emergencyInfo.getTransferPrice() != null) {
            existingInfo.setTransferPrice(emergencyInfo.getTransferPrice());
        if (createVO.getPrice() != null) {
            existingInfo.setTransferPrice(createVO.getPrice());
        }
        // 更新单据类型ID
        if (createVO.getDocumentTypeId() != null) {
            existingInfo.setDocumentTypeId(createVO.getDocumentTypeId());
        }
        // 更新任务类型ID
        if (createVO.getTaskTypeId() != null) {
            existingInfo.setTaskTypeId(createVO.getTaskTypeId());
        }
        
        // 更新病情ID列表
        if (updateVO.getDiseaseIds() != null && !updateVO.getDiseaseIds().isEmpty()) {
            String diseaseIdsStr = updateVO.getDiseaseIds().stream()
        if (createVO.getDiseaseIds() != null && !createVO.getDiseaseIds().isEmpty()) {
            String diseaseIdsStr = createVO.getDiseaseIds().stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
            existingInfo.setDiseaseIds(diseaseIdsStr);
        } else {
            // 如果病情ID列表为空,清空该字段
            existingInfo.setDiseaseIds(null);
        }
        
        // 系统字段
        existingInfo.setUpdateTime(DateUtils.getNowDate());
        existingInfo.setUpdateBy(SecurityUtils.getUsername());
        existingInfo.setUpdateBy(userName);
        
        // 执行更新
        sysTaskEmergencyMapper.updateSysTaskEmergency(existingInfo);
    }
    /**
     * 从 TaskCreateVO 更新福祉车任务扩展信息
     *
     * @param taskId 任务ID
     * @param createVO 任务创建/更新对象
     * @param userName 操作人名
     */
    private void updateWelfareInfoFromCreateVO(Long taskId, TaskCreateVO createVO, String userName) {
        // 查询现有的扩展信息
        SysTaskWelfare existingInfo = sysTaskWelfareMapper.selectSysTaskWelfareByTaskId(taskId);
        if (existingInfo == null) {
            // 如果不存在,则创建新的
            existingInfo = new SysTaskWelfare();
            existingInfo.setTaskId(taskId);
            existingInfo.setCreateTime(DateUtils.getNowDate());
            existingInfo.setCreateBy(userName);
        }
        // 更新乘客信息
        if (createVO.getPassenger() != null) {
            if (createVO.getPassenger().getContact() != null) {
                existingInfo.setPassengerContact(createVO.getPassenger().getContact());
            }
            if (createVO.getPassenger().getPhone() != null) {
                existingInfo.setPassengerPhone(createVO.getPassenger().getPhone());
            }
        }
        // 更新地址信息
        if (createVO.getStartAddress() != null) {
            existingInfo.setPickupAddress(createVO.getStartAddress());
        }
        if (createVO.getEndAddress() != null) {
            existingInfo.setDestinationAddress(createVO.getEndAddress());
        }
        // 更新GPS坐标
        if (createVO.getDepartureLongitude() != null) {
            existingInfo.setPickupLongitude(createVO.getDepartureLongitude());
        }
        if (createVO.getDepartureLatitude() != null) {
            existingInfo.setPickupLatitude(createVO.getDepartureLatitude());
        }
        if (createVO.getDestinationLongitude() != null) {
            existingInfo.setDestinationLongitude(createVO.getDestinationLongitude());
        }
        if (createVO.getDestinationLatitude() != null) {
            existingInfo.setDestinationLatitude(createVO.getDestinationLatitude());
        }
        // 更新距离和费用
        if (createVO.getDistance() != null) {
            existingInfo.setServiceDistance(createVO.getDistance());
        } else if (createVO.getEstimatedDistance() != null) {
            existingInfo.setServiceDistance(createVO.getEstimatedDistance());
        }
        if (createVO.getPrice() != null) {
            existingInfo.setServicePrice(createVO.getPrice());
        }
        // 系统字段
        existingInfo.setUpdateTime(DateUtils.getNowDate());
        existingInfo.setUpdateBy(userName);
        // 执行更新
        sysTaskWelfareMapper.updateSysTaskWelfare(existingInfo);
    }
    /**
     * 保存福祉车任务扩展信息
ruoyi-system/src/main/resources/mapper/system/LegacyTransferSyncMapper.xml
@@ -48,6 +48,7 @@
    <!-- 执行人结果映射 -->
    <resultMap type="java.util.HashMap" id="AssigneeResult">
        <result property="EntourageOAId" column="EntourageOAId" />
        <result property="EntourageID" column="EntourageID" />
        <result property="EntourageState" column="EntourageState" />
    </resultMap>
    
@@ -156,6 +157,7 @@
    <select id="selectAssigneesByDispatchOrdID" resultMap="AssigneeResult">
        SELECT 
            EntourageOAId,
            EntourageID,
            EntourageState
        FROM DispatchOrd_Entourage 
        WHERE DispatchOrdIDDt = #{dispatchOrdID}
ruoyi-system/src/main/resources/mapper/system/SysTaskAdditionalFeeMapper.xml
@@ -41,6 +41,9 @@
            <if test="quantity != null">quantity,</if>
            <if test="totalAmount != null">total_amount,</if>
            <if test="remark != null">remark,</if>
            <if test="pid != null">pid,</if>
            <if test="syncStatus != null">sync_status,</if>
            <if test="syncTime != null">sync_time,</if>
            <if test="createdBy != null and createdBy != ''">created_by,</if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
@@ -51,6 +54,9 @@
            <if test="quantity != null">#{quantity},</if>
            <if test="totalAmount != null">#{totalAmount},</if>
            <if test="remark != null">#{remark},</if>
            <if test="pid != null">#{pid},</if>
            <if test="syncStatus != null">#{syncStatus},</if>
            <if test="syncTime != null">#{syncTime},</if>
            <if test="createdBy != null and createdBy != ''">#{createdBy},</if>
        </trim>
    </insert>
ruoyi-system/src/main/resources/mapper/system/SysTaskMapper.xml
@@ -251,6 +251,7 @@
            <if test="actualEndTime != null">actual_end_time = #{actualEndTime},</if>
            <if test="creatorId != null">creator_id = #{creatorId},</if>
            <if test="assigneeId != null">assignee_id = #{assigneeId},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
ruoyi-ui/src/views/task/general/detail.vue
@@ -33,11 +33,11 @@
      <!-- 急救转运任务扩展信息 -->
      <el-descriptions v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && taskDetail.emergencyInfo" title="急救转运信息" :column="2" border style="margin-top: 20px;">
        <el-descriptions-item label="联系人">
          <span v-if="taskDetail.emergencyInfo.contactPerson">{{ taskDetail.emergencyInfo.contactPerson }}</span>
          <span v-if="taskDetail.emergencyInfo.patientContact">{{ taskDetail.emergencyInfo.patientContact }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="联系电话">
          <span v-if="taskDetail.emergencyInfo.contactPhone">{{ taskDetail.emergencyInfo.contactPhone }}</span>
          <span v-if="taskDetail.emergencyInfo.patientPhone">{{ taskDetail.emergencyInfo.patientPhone }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="患者姓名">
@@ -53,7 +53,7 @@
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
        <el-descriptions-item label="病情描述" :span="2">
          <span v-if="taskDetail.emergencyInfo.illnessDescription">{{ taskDetail.emergencyInfo.illnessDescription }}</span>
          <span v-if="taskDetail.emergencyInfo.patientCondition">{{ taskDetail.emergencyInfo.patientCondition }}</span>
          <span v-else style="color: #C0C4CC;">--</span>
        </el-descriptions-item>
      </el-descriptions>
@@ -120,6 +120,166 @@
        </el-descriptions-item>
      </el-descriptions>
      <!-- 支付信息(仅急救转运任务显示) -->
      <el-card v-if="taskDetail.taskType === 'EMERGENCY_TRANSFER' && paymentInfo" class="box-card" style="margin-top: 20px;">
        <div slot="header" class="clearfix">
          <span>支付信息</span>
        </div>
        <!-- 支付概览 -->
        <el-descriptions :column="3" border>
          <el-descriptions-item label="基本价格">
            <span style="color: #409EFF; font-weight: bold;">¥{{ paymentInfo.transferPrice || '0.00' }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="附加费用总额">
            <span style="color: #E6A23C; font-weight: bold;">¥{{ paymentInfo.additionalAmount || '0.00' }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="总金额">
            <span style="color: #67C23A; font-weight: bold;">¥{{ paymentInfo.totalAmount || '0.00' }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="已支付金额">
            <span style="color: #67C23A; font-weight: bold;">¥{{ paymentInfo.paidAmount || '0.00' }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="剩余未付金额">
            <span style="color: #F56C6C; font-weight: bold;">¥{{ getRemainingAmount() }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="支付状态">
            <el-tag v-if="getRemainingAmount() <= 0" type="success" size="small">
              <i class="el-icon-success"></i> 已结清
            </el-tag>
            <el-tag v-else-if="paymentInfo.paidAmount > 0" type="warning" size="small">
              <i class="el-icon-warning"></i> 部分支付
            </el-tag>
            <el-tag v-else type="info" size="small">
              <i class="el-icon-warning"></i> 未支付
            </el-tag>
          </el-descriptions-item>
        </el-descriptions>
        <!-- 附加费用明细 -->
        <div style="margin-top: 20px;">
          <h4 style="margin-bottom: 10px;">附加费用明细</h4>
          <el-table :data="paymentInfo.additionalFees" border>
            <el-table-column label="费用类型" align="center" prop="feeType" width="120">
              <template slot-scope="scope">
                <dict-tag :options="dict.type.task_additional_fee_type" :value="scope.row.feeType"/>
              </template>
            </el-table-column>
            <el-table-column label="费用名称" align="center" prop="feeName" />
            <el-table-column label="单价" align="center" prop="unitAmount" width="120">
              <template slot-scope="scope">
                <span>¥{{ scope.row.unitAmount }}</span>
              </template>
            </el-table-column>
            <el-table-column label="数量" align="center" prop="quantity" width="80" />
            <el-table-column label="小计" align="center" prop="totalAmount" width="120">
              <template slot-scope="scope">
                <span style="color: #E6A23C; font-weight: bold;">¥{{ scope.row.totalAmount }}</span>
              </template>
            </el-table-column>
            <el-table-column label="同步状态" align="center" width="120">
              <template slot-scope="scope">
                <el-tag v-if="scope.row.syncStatus === 0" type="info" size="small">
                  <i class="el-icon-warning"></i> 未同步
                </el-tag>
                <el-tag v-else-if="scope.row.syncStatus === 1" type="warning" size="small">
                  <i class="el-icon-loading"></i> 同步中
                </el-tag>
                <el-tag v-else-if="scope.row.syncStatus === 2" type="success" size="small">
                  <i class="el-icon-success"></i> 同步成功
                </el-tag>
                <el-tag v-else-if="scope.row.syncStatus === 3" type="danger" size="small">
                  <i class="el-icon-error"></i> 同步失败
                </el-tag>
                <span v-else style="color: #C0C4CC;">--</span>
              </template>
            </el-table-column>
            <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip>
              <template slot-scope="scope">
                <span v-if="scope.row.remark">{{ scope.row.remark }}</span>
                <span v-else style="color: #C0C4CC;">--</span>
              </template>
            </el-table-column>
          </el-table>
          <!-- 空状态提示 -->
          <div v-if="!paymentInfo.additionalFees || paymentInfo.additionalFees.length === 0" style="text-align: center; padding: 40px 0; color: #909399;">
            <i class="el-icon-document" style="font-size: 48px; display: block; margin-bottom: 12px;"></i>
            <span>暂无附加费用</span>
          </div>
        </div>
        <!-- 已支付记录 -->
        <div style="margin-top: 20px;">
          <h4 style="margin-bottom: 10px;">已支付记录</h4>
          <el-table :data="paymentInfo.paidPayments" border>
            <el-table-column label="商户订单号" align="center" prop="outTradeNo" width="200" show-overflow-tooltip />
            <el-table-column label="支付金额" align="center" prop="settlementAmount" width="120">
              <template slot-scope="scope">
                <span style="color: #67C23A; font-weight: bold;">¥{{ scope.row.settlementAmount }}</span>
              </template>
            </el-table-column>
            <el-table-column label="支付方式" align="center" prop="paymentMethod" width="120">
              <template slot-scope="scope">
                <dict-tag :options="dict.type.task_payment_method" :value="scope.row.paymentMethod"/>
              </template>
            </el-table-column>
            <el-table-column label="支付状态" align="center" prop="payStatus" width="120">
              <template slot-scope="scope">
                <el-tag v-if="scope.row.payStatus === 'PAID'" type="success" size="small">
                  <i class="el-icon-success"></i> 已支付
                </el-tag>
                <el-tag v-else-if="scope.row.payStatus === 'PENDING'" type="warning" size="small">
                  <i class="el-icon-time"></i> 待支付
                </el-tag>
                <el-tag v-else-if="scope.row.payStatus === 'FAILED'" type="danger" size="small">
                  <i class="el-icon-error"></i> 失败
                </el-tag>
                <el-tag v-else type="info" size="small">
                  {{ scope.row.payStatus }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="交易号" align="center" prop="tradeNo" width="150" show-overflow-tooltip />
            <el-table-column label="支付时间" align="center" prop="payTime" width="180">
              <template slot-scope="scope">
                <span v-if="scope.row.payTime">{{ parseTime(scope.row.payTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
                <span v-else style="color: #C0C4CC;">--</span>
              </template>
            </el-table-column>
            <el-table-column label="同步状态" align="center" width="120">
              <template slot-scope="scope">
                <el-tag v-if="scope.row.syncStatus === 0" type="info" size="small">
                  <i class="el-icon-warning"></i> 未同步
                </el-tag>
                <el-tag v-else-if="scope.row.syncStatus === 1" type="warning" size="small">
                  <i class="el-icon-loading"></i> 同步中
                </el-tag>
                <el-tag v-else-if="scope.row.syncStatus === 2" type="success" size="small">
                  <i class="el-icon-success"></i> 同步成功
                </el-tag>
                <el-tag v-else-if="scope.row.syncStatus === 3" type="danger" size="small">
                  <i class="el-icon-error"></i> 同步失败
                </el-tag>
                <span v-else style="color: #C0C4CC;">--</span>
              </template>
            </el-table-column>
            <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip>
              <template slot-scope="scope">
                <span v-if="scope.row.remark">{{ scope.row.remark }}</span>
                <span v-else style="color: #C0C4CC;">--</span>
              </template>
            </el-table-column>
          </el-table>
          <!-- 空状态提示 -->
          <div v-if="!paymentInfo.paidPayments || paymentInfo.paidPayments.length === 0" style="text-align: center; padding: 40px 0; color: #909399;">
            <i class="el-icon-document" style="font-size: 48px; display: block; margin-bottom: 12px;"></i>
            <span>暂无支付记录</span>
          </div>
        </div>
      </el-card>
      <!-- 福祉车任务扩展信息 -->
      <el-descriptions v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo" title="福祉车服务信息" :column="2" border style="margin-top: 20px;">
        <el-descriptions-item label="乘客姓名">{{ taskDetail.welfareInfo.passengerName }}</el-descriptions-item>
@@ -151,6 +311,48 @@
        <el-button type="success" @click="handleAssign" v-hasPermi="['task:general:assign']">分配任务</el-button>
        <el-button type="warning" @click="handleStatusChange" v-hasPermi="['task:general:status']">状态变更</el-button>
        <el-button type="info" @click="handleVehicleAssign" v-hasPermi="['task:general:assign']">分配车辆</el-button>
      </div>
    </el-card>
    <!-- 执行人员列表 -->
    <el-card class="box-card" style="margin-top: 20px;">
      <div slot="header" class="clearfix">
        <span>执行人员</span>
      </div>
      <el-table :data="taskDetail.assignees" v-loading="assigneeLoading">
        <el-table-column label="序号" align="center" width="60">
          <template slot-scope="scope">
            <span>{{ scope.$index + 1 }}</span>
          </template>
        </el-table-column>
        <el-table-column label="姓名" align="center" prop="userName">
          <template slot-scope="scope">
            <span>{{ scope.row.userName }}</span>
            <el-tag v-if="scope.row.isPrimary === '1'" type="warning" size="mini" style="margin-left: 8px;">
              <i class="el-icon-star-on"></i> 负责人
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="角色类型" align="center" prop="userType" width="120">
          <template slot-scope="scope">
            <el-tag v-if="scope.row.userType === 'driver'" type="primary" size="small">司机</el-tag>
            <el-tag v-else-if="scope.row.userType === 'doctor'" type="success" size="small">医生</el-tag>
            <el-tag v-else-if="scope.row.userType === 'nurse'" type="info" size="small">护士</el-tag>
            <el-tag v-else type="" size="small">{{ scope.row.userType }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime" width="180">
          <template slot-scope="scope">
            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
          </template>
        </el-table-column>
      </el-table>
      <!-- 空状态提示 -->
      <div v-if="!taskDetail.assignees || taskDetail.assignees.length === 0" style="text-align: center; padding: 40px 0; color: #909399;">
        <i class="el-icon-user" style="font-size: 48px; display: block; margin-bottom: 12px;"></i>
        <span>暂无执行人员</span>
      </div>
    </el-card>
@@ -261,6 +463,74 @@
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- 附加费用列表 -->
    <el-card class="box-card" style="margin-top: 20px;">
      <div slot="header" class="clearfix">
        <span>附加费用列表</span>
      </div>
      <el-table :data="additionalFeeList" v-loading="feeLoading">
        <el-table-column label="费用类型" align="center" prop="feeType" width="120">
          <template slot-scope="scope">
            <dict-tag :options="dict.type.task_additional_fee_type" :value="scope.row.feeType"/>
          </template>
        </el-table-column>
        <el-table-column label="费用名称" align="center" prop="feeName" />
        <el-table-column label="单价" align="center" prop="unitAmount" width="120">
          <template slot-scope="scope">
            <span>¥{{ scope.row.unitAmount }}</span>
          </template>
        </el-table-column>
        <el-table-column label="数量" align="center" prop="quantity" width="80" />
        <el-table-column label="总金额" align="center" prop="totalAmount" width="120">
          <template slot-scope="scope">
            <span style="color: #E6A23C; font-weight: bold;">¥{{ scope.row.totalAmount }}</span>
          </template>
        </el-table-column>
        <el-table-column label="同步状态" align="center" width="120">
          <template slot-scope="scope">
            <el-tag v-if="scope.row.syncStatus === 0" type="info" size="small">
              <i class="el-icon-warning"></i> 未同步
            </el-tag>
            <el-tag v-else-if="scope.row.syncStatus === 1" type="warning" size="small">
              <i class="el-icon-loading"></i> 同步中
            </el-tag>
            <el-tag v-else-if="scope.row.syncStatus === 2" type="success" size="small">
              <i class="el-icon-success"></i> 同步成功
            </el-tag>
            <el-tag v-else-if="scope.row.syncStatus === 3" type="danger" size="small">
              <i class="el-icon-error"></i> 同步失败
            </el-tag>
            <span v-else style="color: #C0C4CC;">--</span>
          </template>
        </el-table-column>
        <el-table-column label="同步时间" align="center" prop="syncTime" width="180">
          <template slot-scope="scope">
            <span v-if="scope.row.syncTime">{{ parseTime(scope.row.syncTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
            <span v-else style="color: #C0C4CC;">--</span>
          </template>
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createdTime" width="180">
          <template slot-scope="scope">
            <span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
          </template>
        </el-table-column>
        <el-table-column label="创建者" align="center" prop="createdBy" width="100" />
        <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip>
          <template slot-scope="scope">
            <span v-if="scope.row.remark">{{ scope.row.remark }}</span>
            <span v-else style="color: #C0C4CC;">--</span>
          </template>
        </el-table-column>
      </el-table>
      <!-- 空状态提示 -->
      <div v-if="!additionalFeeList || additionalFeeList.length === 0" style="text-align: center; padding: 40px 0; color: #909399;">
        <i class="el-icon-document" style="font-size: 48px; display: block; margin-bottom: 12px;"></i>
        <span>暂无附加费用</span>
      </div>
    </el-card>
    <!-- 操作日志 -->
@@ -456,13 +726,13 @@
</template>
<script>
import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask } from "@/api/task";
import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo } from "@/api/task";
import { listUser } from "@/api/system/user";
import { getToken } from "@/utils/auth";
export default {
  name: "TaskDetail",
  dicts: ['sys_task_type', 'sys_task_status', 'sys_vehicle_type', 'sys_task_vehicle_status', 'sys_user_sex', 'hospital_department', 'sys_attachment_category'],
  dicts: ['sys_task_type', 'sys_task_status', 'sys_vehicle_type', 'sys_task_vehicle_status', 'sys_user_sex', 'hospital_department', 'sys_attachment_category', 'task_additional_fee_type', 'task_payment_method'],
  data() {
    return {
      // 任务详情
@@ -502,6 +772,12 @@
      // 加载状态
      vehicleLoading: false,
      attachmentLoading: false,
      feeLoading: false,
      assigneeLoading: false,
      // 附加费用列表
      additionalFeeList: [],
      // 支付信息
      paymentInfo: null,
      // 上传相关
      uploadUrl: process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + (new URLSearchParams(window.location.search).get('taskId') || ''),
      uploadHeaders: {
@@ -545,6 +821,7 @@
  created() {
    this.getTaskDetail();
    this.getUserList();
    this.getAdditionalFeeList();
    // 初始化上传URL
    this.uploadUrl = process.env.VUE_APP_BASE_API + "/task/attachment/upload/" + this.$route.params.taskId;
  },
@@ -553,8 +830,41 @@
    getTaskDetail() {
      getTask(this.$route.params.taskId).then(response => {
        this.taskDetail = response.data;
        // 任务详情加载完成后,加载支付信息
        // console.log("TaskDetail", this.taskDetail);
        this.loadPaymentInfo();
      });
    },
    /** 获取附加费用列表 */
    getAdditionalFeeList() {
      this.feeLoading = true;
      getPaymentInfo(this.$route.params.taskId).then(response => {
        this.additionalFeeList = response.data.additionalFees || [];
        this.feeLoading = false;
      }).catch(() => {
        this.feeLoading = false;
      });
    },
    /** 加载支付信息 */
    loadPaymentInfo() {
      //EMERGENCY_TRANSFER
      if (this.taskDetail.taskType === 'EMERGENCY_TRANSFER') {
        getPaymentInfo(this.$route.params.taskId).then(response => {
          // console.log("PaymentInfo", response.data);
          this.paymentInfo = response.data;
        }).catch(() => {
          this.paymentInfo = null;
        });
      }
    },
    /** 计算剩余未付金额 */
    getRemainingAmount() {
      if (!this.paymentInfo) return '0.00';
      const total = parseFloat(this.paymentInfo.totalAmount || 0);
      const paid = parseFloat(this.paymentInfo.paidAmount || 0);
      const remaining = total - paid;
      return remaining > 0 ? remaining.toFixed(2) : '0.00';
    },
    /** 获取用户列表 */
    getUserList() {
      listUser().then(response => {