doum
12 小时以前 46124fe454f90d24171ebc5be0d9cfe2ab22cbc5
最新版本541200007
已添加3个文件
已删除1个文件
已修改27个文件
1754 ■■■■ 文件已修改
admin/src/api/business/jkSketchCustomer.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/business/OperaJkSketchCustomerWindow.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/business/OperaJkSketchLineListWindow.vue 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/business/OperaJkSketchResultWindow.vue 276 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/components/business/OperaJkSketchWindow.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/business/jkCustomer.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/business/jkLine.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/views/business/jkSketch.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/pom.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/pom.xml 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/src/main/java/com/doumee/core/utils/Constants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/src/main/java/com/doumee/core/utils/DateUtil.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/src/main/java/com/doumee/core/utils/DistanceCalculator.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/src/main/java/com/doumee/core/utils/TspSolver.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/src/main/java/com/doumee/core/utils/tsp/TspSolver.java 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/system_service/src/main/java/com/doumee/core/utils/tsp/TspSolverSolutions.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_admin/pom.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/JkSketchCloudController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/JkSketchCustomerCloudController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/pom.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkLine.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkOrders.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkSketch.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkSketchCustomer.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkSketchLine.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/JkSketchService.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkCustomerServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkLineServiceImpl.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkSketchCustomerServiceImpl.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkSketchLineServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkSketchServiceImpl.java 349 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
admin/src/api/business/jkSketchCustomer.js
@@ -7,6 +7,13 @@
  })
}
// æŸ¥è¯¢
export function allList (data) {
  return request.post('/visitsAdmin/cloudService/business/jkSketchCustomer/allList', data, {
    trim: true
  })
}
// å¯¼å‡ºExcel
export function exportExcel (data) {
  return request.post('/visitsAdmin/cloudService/business/jkSketchCustomer/exportExcel', data, {
admin/src/components/business/OperaJkSketchCustomerWindow.vue
@@ -1,86 +1,104 @@
<template>
  <GlobalWindow
    :title="title"
    width="85%"
    :visible.sync="visible"
    :confirm-working="isWorking"
    @confirm="confirm"
  >
    <el-form :model="form" ref="form" :rules="rules">
      <el-form-item label="创建人编码" prop="creator">
        <el-input v-model="form.creator" placeholder="请输入创建人编码" v-trim/>
      </el-form-item>
      <el-form-item label="创建时间" prop="createDate">
        <el-date-picker v-model="form.createDate" value-format="yyyy-MM-dd" placeholder="请输入创建时间"></el-date-picker>
      </el-form-item>
      <el-form-item label="更新人编码" prop="editor">
        <el-input v-model="form.editor" placeholder="请输入更新人编码" v-trim/>
      </el-form-item>
      <el-form-item label="更新时间" prop="editDate">
        <el-date-picker v-model="form.editDate" value-format="yyyy-MM-dd" placeholder="请输入更新时间"></el-date-picker>
      </el-form-item>
      <el-form-item label="是否删除0否 1是" prop="isdeleted">
        <el-input v-model="form.isdeleted" placeholder="请输入是否删除0否 1是" v-trim/>
      </el-form-item>
      <el-form-item label="备注" prop="info">
        <el-input v-model="form.info" placeholder="请输入备注" v-trim/>
      </el-form-item>
      <el-form-item label="线路编码(关联jk_sketch_line)" prop="sketchLineId">
        <el-input v-model="form.sketchLineId" placeholder="请输入线路编码(关联jk_sketch_line)" v-trim/>
      </el-form-item>
      <el-form-item label="线路优化编码(关联jk_sketch)" prop="sketchId">
        <el-input v-model="form.sketchId" placeholder="请输入线路优化编码(关联jk_sketch)" v-trim/>
      </el-form-item>
      <el-form-item label="送货量" prop="totalNum">
        <el-input v-model="form.totalNum" placeholder="请输入送货量" v-trim/>
      </el-form-item>
      <el-form-item label="客户数" prop="orderNum">
        <el-input v-model="form.orderNum" placeholder="请输入客户数" v-trim/>
      </el-form-item>
      <el-form-item label="排序码" prop="sortnum">
        <el-input v-model="form.sortnum" placeholder="请输入排序码" v-trim/>
      </el-form-item>
      <el-form-item label="日期" prop="dateInfo">
        <el-date-picker v-model="form.dateInfo" value-format="yyyy-MM-dd" placeholder="请输入日期"></el-date-picker>
      </el-form-item>
    </el-form>
    <TableLayout >
      <!-- æœç´¢è¡¨å• -->
      <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
        <el-form-item label="客户名称" prop="name">
          <el-input v-model="searchForm.name" placeholder="请输入客户名称" @keypress.enter.native="search"></el-input>
        </el-form-item>
        <el-form-item label="客户简码" prop="code">
          <el-input v-model="searchForm.code" placeholder="请输入客户简码" @keypress.enter.native="search"></el-input>
        </el-form-item>
        <section>
          <el-button type="primary" @click="search">搜索</el-button>
          <el-button @click="reset">重置</el-button>
        </section>
      </el-form>
      <!-- è¡¨æ ¼å’Œåˆ†é¡µ -->
      <template v-slot:table-wrap>
        <el-table
            :height="tableHeightNew"
            v-loading="isWorking.search"
            :data="tableData.list"
            stripe
            @selection-change="handleSelectionChange"
        >
          <el-table-column prop="sortnum" label="客户序号" min-width="100px"></el-table-column>
          <el-table-column prop="code" label="客户简码" min-width="120px"></el-table-column>
          <el-table-column prop="name" label="客户名称" min-width="120px"></el-table-column>
          <el-table-column prop="lineName" label="线路名称" min-width="180px" show-tooltip-when-overflow></el-table-column>
          <el-table-column prop="totalNum" label="送货量(条)" min-width="120px"></el-table-column>
          <el-table-column prop="dateInfo" label="送货日期" min-width="120px"></el-table-column>
        </el-table>
        <pagination
            @size-change="handleSizeChange"
            @current-change="handlePageChange"
            :pagination="tableData.pagination"
        >
        </pagination>
      </template>
    </TableLayout>
    <template   v-slot:footer>
      <el-button @click="visible=false">返回</el-button>
    </template>
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import BaseTable from '@/components/base/BaseTable'
import TableLayout from '@/layouts/TableLayout'
import Pagination from '@/components/common/Pagination'
import GlobalWindow from '@/components/common/GlobalWindow'
export default {
  name: 'OperaJkSketchCustomerWindow',
  extends: BaseOpera,
  components: { GlobalWindow },
  extends: BaseTable,
  components: { GlobalWindow, TableLayout, Pagination },
  data () {
    return {
      // è¡¨å•数据
      form: {
        id: null,
        creator: '',
        createDate: '',
        editor: '',
        editDate: '',
        isdeleted: '',
        info: '',
      visible: false,
      title: '',
      searchForm: {
        sketchLineId: '',
        sketchId: '',
        totalNum: '',
        orderNum: '',
        sortnum: '',
        dateInfo: ''
      },
      // éªŒè¯è§„则
      rules: {
        name: '',
        code: ''
      }
    }
  },
  created () {
    this.config({
      module: '交控-线路优化线路客户记录信息表',
      api: '/business/jkSketchCustomer',
      'field.id': 'id'
      'field.id': 'id',
      'field.main': 'id'
    })
    this.search()
  },
  methods: {
    open (title, row) {
      this.title = title + (row.lineName)
      this.searchForm.sketchLineId = row.id
      this.visible = true
      this.tableData = {
        // å·²é€‰ä¸­çš„æ•°æ®
        selectedRows: [],
        // æŽ’序的字段
        sorts: [],
        // å½“前页数据
        list: [],
        // åˆ†é¡µ
        pagination: {
          pageIndex: 1,
          pageSize: 10,
          total: 0
        }
      }
      this.search()
    }
  }
}
</script>
admin/src/components/business/OperaJkSketchLineListWindow.vue
@@ -18,32 +18,52 @@
    <div style="display: block">
      <div  style="display: block;font-size: 16px;font-weight: 600;margin-bottom: 10px;">线路明细</div>
      <div>
        <el-table  :data="dataList" stripe  >
        <el-table  :data="dataList" stripe>
          <el-table-column prop="dateInfo" label="送货日期" min-width="130px"></el-table-column>
          <el-table-column prop="lineName" label="送货线路" min-width="130px">  </el-table-column>
          <el-table-column prop="orderNum" label="客户数(户)" min-width="130px"></el-table-column>
          <el-table-column prop="totalNum" label="送货量(条)" min-width="130px"></el-table-column>
          <el-table-column prop="carCode" label="车牌号" min-width="100px"></el-table-column>
          <el-table-column prop="memberName" label="送货司机" min-width="100px"></el-table-column>
          <el-table-column prop="distance" label="总路程(公里)" min-width="100px">
            <template slot-scope="{row}">
              {{((row.distance ||0)/1000).toFixed(2)}}
            </template>
          </el-table-column>
          <el-table-column
              label="操作"
              min-width="120"
              align="center"
              fixed="right"
          >
            <template slot-scope="{row}">
              <el-button type="text" @click="$refs.operaJkSketchCustomerWindow.open('线路客户明细——', row)" icon="el-icon-view"  >查看客户</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <template   v-slot:footer>
      <el-button @click="visible=false">返回</el-button>
    </template>
    <OperaJkSketchCustomerWindow ref="operaJkSketchCustomerWindow"  />
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
import OperaJkSketchCustomerWindow from '@/components/business/OperaJkSketchCustomerWindow'
export default {
  name: 'OperaJkSketchLineWindow',
  extends: BaseOpera,
  components: { GlobalWindow },
  components: { GlobalWindow, OperaJkSketchCustomerWindow},
  data () {
    return {
      // è¡¨å•数据
      model: {
      },
      dataList:[],
      dataList:[]
    }
  },
  created () {
@@ -66,6 +86,9 @@
        this.dataList = res
      })
    },
    viewCustomer(row){
    }
  }
}
</script>
admin/src/components/business/OperaJkSketchResultWindow.vue
@@ -1,82 +1,262 @@
<template>
  <el-dialog
      class="center-title"
  <GlobalWindow
      :title="title"
      width="500px"
      top="30vh"
      width="100%"
      :visible.sync="visible"
      :confirm-working="isWorking"
      @confirm="confirm"
  >
    <p class="tip-warn"><i class="el-icon-warning"></i>导入说明:<br>
      1.请先下载文件模板,并按照模板要去填写表格内容;<br>
      2.每次导入销售订单表示即删除之前导入此次相应日期的订单记录,以此次导入的数据为主;<br>
    </p>
    <el-form class="demo-form-inline" >
      <el-form-item label="线路信息" required>
        <div style="width: 100%;display: flex;align-items: center;">
          <el-button type="primary"   @click="clickRef">点击上传</el-button>
          <el-button type="text" @click="exportTemplate">点击下载模版.EXCEL</el-button>
    <div  style="display: block;margin-bottom: 30px;">
      <div style="display: block;font-size: 16px;font-weight: 600;margin-bottom: 20px;">主线路信息</div>
      <div style="display: flex;">
        <div style="flex: 1">送货日期:{{model.dateInfo ||''}}</div>
        <div style="flex: 1">主线路:{{model.categoryName ||''}}</div>
        <div style="flex: 1">客户数:{{model.orderNum ||'-'}}</div>
        <div style="flex: 1">送货量(条):{{model.totalNum ||'-'}}</div>
      </div>
      <div style="display: flex;margin-top: 30px">
        <div style="flex: 1">优化时间:{{model.planLineDate ||''}} - {{model.planLineEndDate ||''}}</div>
      </div>
      <div style="display: flex;margin-top: 20px" class="orange">
        <div style="flex: 1">优化结果:优化前总路程 <span  class="red" style="font-weight: bold"> {{((model.originDistance ||0)/1000).toFixed(2)}}</span> å…¬é‡Œï¼Œä¼˜åŒ–后总路程<span class="green" style="font-weight: bold"> {{((model.distance ||0)/1000).toFixed(2)}} </span> å…¬é‡Œ</div>
      </div>
    </div>
    <div style="display: block">
      <div  style="display: block;font-size: 16px;font-weight: 600;margin-bottom: 10px;">线路明细</div>
      <div>
        <el-table  :data="dataList" stripe  :row-class-name="tableRowClassName">
          <el-table-column prop="dateInfo" label="送货日期" min-width="130px"></el-table-column>
          <el-table-column prop="lineName" label="送货线路" min-width="130px">  </el-table-column>
          <el-table-column prop="orderNum" label="客户数(户)" min-width="130px"> </el-table-column>
          <el-table-column prop="totalNum" label="送货量(条)" min-width="130px"></el-table-column>
          <el-table-column prop="carCode" label="车牌号" min-width="100px"></el-table-column>
          <el-table-column prop="memberName" label="送货司机" min-width="100px"></el-table-column>
          <el-table-column prop="distance" label="总路程(公里)" min-width="100px">
            <template slot-scope="{row}">
              {{((row.distance ||0)/1000).toFixed(2)}}
            </template>
          </el-table-column>
          <el-table-column
              label="操作"
              min-width="120"
              align="left"
              fixed="right"
          >
            <template slot-scope="{row}">
              <el-button type="text" @click="updateDo(row)" icon="el-icon-edit"   style="color: #0d68ff" v-if="!updating" >微调</el-button>
              <template v-else-if="updating &&  currentRow.id ===row.id" >
                <el-button type="text" style="color: #13ce66"  >本车</el-button>
                <el-button type="text" @click="cancelDo(row)"  icon="el-icon-delete"  style="color: red" >取消</el-button>
              </template>
              <el-button type="text" @click="addDo(row)" icon="el-icon-plus"  style="color: red" v-else-if="updating &&  currentRow.id !==row.id" >加入</el-button>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <div style="display: block;margin-top: 30px;border: 1px solid #f2f2f2;padding: 10px" v-if="updating">
      <div  style="display: block;font-size: 16px;font-weight: 600;margin-bottom: 10px;">{{currentRow.lineName}} - å®¢æˆ·æ˜Žç»†</div>
      <div>
        <p class="tip-warn"><i class="el-icon-warning"></i>操作说明:<br>
          1.请选择若干客户信息,点击上述其他线路后的<span class="red">【加入】</span>按钮,将选中的客户移加到对应线路中;<br>
            2.点击本车线路后的<span class="red">【取消】</span>按钮,可撤销本车微调操作;<br>
        </p>
        <el-table  :data="paginatedData" stripe  @selection-change="handleSelectionChange">>
          <el-table-column type="selection" width="55"></el-table-column>
          <el-table-column prop="code" label="客户简码" min-width="130px"></el-table-column>
          <el-table-column prop="name" label="客户名称" min-width="130px">  </el-table-column>
          <el-table-column prop="totalNum" label="送货量(条)" min-width="130px"></el-table-column>
          <el-table-column prop="location" label="客户地址" min-width="200px" show-tooltip-when-overflow></el-table-column>
          <el-table-column prop="latitude" label="经纬度" min-width="200px">
            <template slot-scope="{row}">
               {{(row.longitude || 0).toFixed(6)}},{{(row.latitude || 0).toFixed(6)}}
            </template>
          </el-table-column>
        </el-table>
        <div class="table-pagination">
          <el-pagination
              @current-change="handleCurrentChange"
              :current-page="currentPage"
              :page-size="pageSize"
              layout="total, prev, pager, next, jumper"
              :total="totalItems">
          </el-pagination>
        </div>
        <div style="font-size: 14px; color: black;" v-if="fileName">{{fileName}}</div>
      </el-form-item>
    </el-form>
    <input type="file" style="position: fixed; left: 0; top: -50px;" accept=".xlsx" ref="fileExcel" @change="result" />
      </div>
    </div>
    <template   v-slot:footer>
      <el-button @click="confirmDo" type="primary" v-if="buttonName!==''">{{ buttonName||'确认' }}</el-button>
      <el-button @click="visible=false">返回</el-button>
    </template>
  </el-dialog>
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
import { importExcel } from '@/api/business/jkLine'
import { allList as customerList } from '@/api/business/jkSketchCustomer'
export default {
  name: 'OperaJkSketchLineWindow',
  extends: BaseOpera,
  // eslint-disable-next-line vue/no-unused-components
  components: { GlobalWindow },
  data () {
    return {
      importing:false,
      fileName: ''
      // è¡¨å•数据
      updating: false,
      currentRow: null,
      model: {
      },
      buttonName: '',
      dataList: [],
      currentPage: 1,
      pageSize: 10,
      totalItems: 0, // æ€»æ•°æ®æ¡ç›®æ•°
      allCustomerList: [],
      tableData: [], // æ‰€æœ‰æ•°æ®
      selectRows: []
    }
  },
  created () {
    this.config({
      api: '/business/jkSketchLine',
      'field.id': 'id'
    })
  },
  computed: {
    paginatedData () {
      const start = (this.currentPage - 1) * this.pageSize
      const end = start + this.pageSize
      return this.tableData.slice(start, end)
    }
  },
  methods: {
    open (title) {
      this.title = title
      this.fileName = ''
    confirmDo () {
    },
    open (title, target) {
      this.title = title + target.categoryName
      this.visible = true
      this.model = target
      this.updating = false
      this.selectRows = []
      this.tableData = []
      this.allCustomerList = []
      this.currentRow = null
      this.loadList()
    },
    // å¯¼å‡ºæ¨¡æ¿
    exportTemplate () {
      // æŠ•保申请
      window.open('/template/jkLineTemplate.xlsx')
    updateDo (row) {
      this.currentRow = row
      this.tableData = row.customerList || []
      this.totalItems = this.tableData.length
      this.currentPage = 1
      this.selectRows = []
      this.updating = true
    },
    clickRef () {
      this.$refs.fileExcel.click()
    cancelDo (row) {
      this.selectRows = []
      this.currentRow = null
      this.updating = false
    },
    result (e) {
      const data = new FormData()
      data.append('file', e.target.files[0])
      importExcel(data)
    addDo (row) {
      if (!this.selectRows || !this.selectRows.length) {
        this.$message.error('对不起,请至少选择有一条客户记录加入该路线!')
        return
      }
      const tarray = []
      let tNum = 0
      this.currentRow.customerList.forEach(item => {
        let flag = false
        this.selectRows.forEach(item1 => {
          if (item.id === item1.id) {
            flag = true
          }
        })
        if (!flag) {
          tNum += (item.totalNum || 0)
          tarray.push(item)
        }
      })
      if(tarray.length === 0){
        this.$message.error('对不起,本车线路至少留存一个客户信息,无法全部清空!')
        return
      }
      var rArray =  row.customerList || []
      rArray = rArray.push(...this.selectRows)
      let ttNum = 0
      rArray.forEach(item => {
        ttNum += (item.totalNum || 0)
      })
      if(tarray.length >= (row.maxCustomer||0)){
        this.$message.error('对不起,加入的线路最大支持'+ (row.maxOrderNum||0) + '客户!')
        return
      }
      if(ttNum >= (row.maxOrder||0)){
        this.$message.error('对不起,加入的线路最大支持'+ (row.maxOrder||0) + '送货量!')
        return
      }
      this.currentRow.customerList = tarray
      this.currentRow.orderNum = this.currentRow.customerList.length
      this.currentRow.totalNum = tNum
      row.customerList = rArray
      row.orderNum = row.customerList.length
      row.totalNum = ttNum
      this.currentRow = null
      this.updating = false
      this.buttonName = '保存调整开始优化'
    },
    handleSelectionChange (rows) {
      this.selectRows = rows
    },
    loadCustomerList () {
      customerList({ sketchId: this.model.id })
        .then(res => {
          this.$message.success('导入成功')
          this.$emit('success')
          this.visible = false
          this.allCustomerList = res
          if (this.allCustomerList && this.allCustomerList.length) {
            this.dataList.forEach(item => {
              var tarray = []
              this.allCustomerList.forEach(item1 => {
                if (item.id === item1.sketchLineId) {
                  tarray.push(item1)
                }
              })
              item.customerList = tarray
            })
          }
        })
        .catch(err => {
          // this.$message.error(err)
          this.fileName = ''
        })
        .finally(() => {
          this.$refs.fileExcel.value = null
        })
    },
    loadList () {
      this.api.allList({
        sketchId: this.model.id
      }).then(res => {
        this.dataList = res
        this.loadCustomerList()
      })
    },
    tableRowClassName ({ row, rowIndex }) {
      if (this.currentRow && row.id === this.currentRow.id) {
        return 'warning-row'
      } else {
        return 'success-row'
      }
    },
    handleCurrentChange (newPage) {
      this.currentPage = newPage
    }
  }
}
</script>
<style lang="scss" scoped>
<style lang="scss">
.el-table .warning-row {
  background: rgba(161, 231, 20, 0.2) !important;
  td{
    background: rgba(161, 231, 20, 0.2) !important;
  }
}
.el-table .success-row {
  background: #f0f9eb !important;
  td{
    background: #f0f9eb !important;
  }
}
</style>
admin/src/components/business/OperaJkSketchWindow.vue
@@ -1,57 +1,48 @@
<template>
  <GlobalWindow
    :title="title"
    width="70%"
    :visible.sync="visible"
    :confirm-working="isWorking"
    @confirm="confirm"
  >
    <el-form :model="form" ref="form" :rules="rules">
      <el-form-item label="创建人编码" prop="creator">
        <el-input v-model="form.creator" placeholder="请输入创建人编码" v-trim/>
    <p class="tip-warn"><i class="el-icon-warning"></i>优化说明:<br>
      1、当前优化任务最多支持<span class="orange">【 {{lineNum}} ã€‘</span>条线路;<br>
      2、合理选择线路,选择线路的总客户数和总送货量需要满足当日订单累计值;<br>
      3、强制优化表示忽略该主线优化中的任务,重新开启优化任务(不建议);<br>
    </p>
<!--    <el-form :model="form" ref="form" :rules="rules">
      <el-form-item label="生成线路数量" prop="planLineNum">
        <el-input type="number" disabled  v-model="form.planLineNum" placeholder="请输入线路数" v-trim/>
      </el-form-item>
      <el-form-item label="创建时间" prop="createDate">
        <el-date-picker v-model="form.createDate" value-format="yyyy-MM-dd" placeholder="请输入创建时间"></el-date-picker>
      </el-form-item>
      <el-form-item label="更新人编码" prop="editor">
        <el-input v-model="form.editor" placeholder="请输入更新人编码" v-trim/>
      </el-form-item>
      <el-form-item label="更新时间" prop="editDate">
        <el-date-picker v-model="form.editDate" value-format="yyyy-MM-dd" placeholder="请输入更新时间"></el-date-picker>
      </el-form-item>
      <el-form-item label="是否删除0否 1是" prop="isdeleted">
        <el-input v-model="form.isdeleted" placeholder="请输入是否删除0否 1是" v-trim/>
      </el-form-item>
      <el-form-item label="备注" prop="info">
        <el-input v-model="form.info" placeholder="请输入备注" v-trim/>
      </el-form-item>
      <el-form-item label="主线路编码(关联category)" prop="categoryId">
        <el-input v-model="form.categoryId" placeholder="请输入主线路编码(关联category)" v-trim/>
      </el-form-item>
      <el-form-item label="送货量" prop="totalNum">
        <el-input v-model="form.totalNum" placeholder="请输入送货量" v-trim/>
      </el-form-item>
      <el-form-item label="客户数" prop="orderNum">
        <el-input v-model="form.orderNum" placeholder="请输入客户数" v-trim/>
      </el-form-item>
      <el-form-item label="状态 0未优化 1优化中 2已优化" prop="status">
        <el-input v-model="form.status" placeholder="请输入状态 0未优化 1优化中 2已优化" v-trim/>
      </el-form-item>
      <el-form-item label="排序码" prop="sortnum">
        <el-input v-model="form.sortnum" placeholder="请输入排序码" v-trim/>
      </el-form-item>
      <el-form-item label="日期" prop="dateInfo">
        <el-date-picker v-model="form.dateInfo" value-format="yyyy-MM-dd" placeholder="请输入日期"></el-date-picker>
      </el-form-item>
      <el-form-item label="优化记录编码(作为key值存redis)" prop="jobId">
        <el-input v-model="form.jobId" placeholder="请输入优化记录编码(作为key值存redis)" v-trim/>
      </el-form-item>
    </el-form>
    </el-form>-->
    <div>
      <el-form :model="form" ref="form" :rules="rules">
        <el-form-item label="是否强制优化" prop="forceUpdate">
          <el-radio-group v-model="form.forceUpdate">
            <el-radio :label="0" :value="0">不强制</el-radio>
            <el-radio :label="1" :value="1">强制</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="选择生成线路" prop="lineIdList" >
          <el-table  :data="lineList" stripe    @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55"></el-table-column>
            <el-table-column prop="name" label="送货线路" min-width="130px" show-tooltip-when-overflow>  </el-table-column>
            <el-table-column prop="maxCustomer" label="客户数(户)" min-width="100px"  show-tooltip-when-overflow></el-table-column>
            <el-table-column prop="maxOrder" label="送货量(条)" min-width="100px"  show-tooltip-when-overflow></el-table-column>
            <el-table-column prop="carCode" label="车牌号" min-width="100px"></el-table-column>
            <el-table-column prop="memberName" label="送货司机" min-width="100px"></el-table-column>
          </el-table>
        </el-form-item>
      </el-form>
    </div>
  </GlobalWindow>
</template>
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
import { allList } from '@/api/business/jkLine'
export default {
  name: 'OperaJkSketchWindow',
  extends: BaseOpera,
@@ -61,22 +52,17 @@
      // è¡¨å•数据
      form: {
        id: null,
        creator: '',
        createDate: '',
        editor: '',
        editDate: '',
        isdeleted: '',
        info: '',
        categoryId: '',
        totalNum: '',
        orderNum: '',
        status: '',
        sortnum: '',
        dateInfo: '',
        jobId: ''
        planLineNum: '',
        forceUpdate: 0,
        lineIdList: []
      },
      categoryId: null,
      lineList: [],
      lineNum: null,
      // éªŒè¯è§„则
      rules: {
        lineIdList: [{ required: true, message: '请选择生成线路数量' }],
        forceUpdate: [{ required: true, message: '请选择是否强制优化' }]
      }
    }
  },
@@ -85,6 +71,70 @@
      api: '/business/jkSketch',
      'field.id': 'id'
    })
  },
  methods: {
    handleSelectionChange (rows) {
      this.form.lineIdList = []
      if (rows || rows.length) {
        rows.forEach(item => {
          this.form.lineIdList.push(item.id)
        })
      }
    },
    open (title, target) {
      if (!target || !target.id) {
        return
      }
      this.lineList = []
      this.title = title
      this.visible = true
      this.categoryId = target.categoryId
      this.form.id = target.id
      this.form.planLineNum = target.planLineNum
      this.form.forceUpdate = 0
      this.form.lineIdList = []
      this.loadLines()
    },
    confirm () {
      this.$refs.form.validate((valid) => {
        if (!valid) {
          return
        }
      /*  if (this.form.planLineNum <= 0 || this.form.planLineNum > this.lineNum) {
          this.$message.error('优化线路数必须在[1-' + this.lineNum + ']范围内!')
          return
        }*/
        this.confirmDo()
      })
    },
    confirmDo () {
      // è°ƒç”¨æ–°å»ºæŽ¥å£
      this.isWorking = true
      this.api.updateById({
        id: this.form.id,
        lineIdList: this.form.lineIdList,
        forceUpdate: this.form.forceUpdate
      })
        .then(() => {
          this.visible = false
          this.$tip.apiSuccess('优化任务正在后台执行,请等待优化完成后查看优化结果!')
          this.$emit('success')
        })
        .catch(e => {
          // this.$tip.apiFailed(e)
        })
        .finally(() => {
          this.isWorking = false
        })
    },
    loadLines () {
      allList({
        categoryId: this.categoryId
      }).then(res => {
        this.lineList = res || []
        this.lineNum = this.lineList.length
      })
    }
  }
}
</script>
admin/src/views/business/jkCustomer.vue
@@ -45,6 +45,7 @@
        <li><el-button type="danger" @click="deleteByIdInBatch" icon="el-icon-delete" v-permissions="['business:jkcustomer:delete']">删除</el-button></li>
      </ul>
      <el-table
          :height="tableHeightNew"
        v-loading="isWorking.search"
        :data="tableData.list"
        stripe
admin/src/views/business/jkLine.vue
@@ -31,6 +31,8 @@
        <li><el-button type="danger" @click="deleteByIdInBatch" icon="el-icon-delete" v-permissions="['business:jkline:delete']">删除</el-button></li>
      </ul>
      <el-table
          :height="tableHeightNew"
        v-loading="isWorking.search"
        :data="tableData.list"
        stripe
admin/src/views/business/jkSketch.vue
@@ -38,30 +38,33 @@
<!--
        <el-table-column type="selection" width="55"></el-table-column>
-->
        <el-table-column prop="dateInfo" label="送货日期" min-width="130px"></el-table-column>
        <el-table-column prop="categoryName" label="主线路" min-width="130px">
        <el-table-column prop="dateInfo" label="送货日期" min-width="100px" align="center"></el-table-column>
        <el-table-column prop="categoryName" label="主线路-当前线路数" min-width="200px" align="center">
          <template slot-scope="{row}">
            <span class="blue" style="cursor: pointer" @click="$refs.operaJkSketchLineListWindow.open('配送线路明细', row)">{{ row.categoryName }}({{row.lineNum||0}}条线路)</span>
            <span class="blue" style="cursor: pointer" @click="$refs.operaJkSketchLineListWindow.open('配送线路明细', row)">{{ row.categoryName }}</span>
            <span class="orange" style="margin-left: 10px">【 {{row.lineNum||0}} ã€‘条线路</span>
          </template>
        </el-table-column>
        <el-table-column prop="orderNum" label="客户数(户)" min-width="130px"></el-table-column>
        <el-table-column prop="totalNum" label="送货量(条)" min-width="130px"></el-table-column>
        <el-table-column prop="status" label="优化状态" min-width="100px">
        <el-table-column prop="orderNum" label="客户数(户)" min-width="100px" align="center"></el-table-column>
        <el-table-column prop="totalNum" label="送货量(条)" min-width="100px" align="center"></el-table-column>
        <el-table-column prop="status" label="优化状态" min-width="100px" align="center">
          <template slot-scope="{row}">
            <span v-if="row.status === 0" class="blue">未优化</span>
            <span v-if="row.status === 1" class="red">优化中</span>
            <span v-if="row.status === 1" class="red">优化中 ã€ {{row.planLineNum||0}} ã€‘条线路</span>
            <span v-if="row.status === 2" class="green">已优化</span>
            <span v-if="row.status === 3" class="green">优化失败</span>
          </template>
        </el-table-column>
        <el-table-column
          v-if="containPermissions(['business:jksketch:update', 'business:jksketch:delete'])"
          label="操作"
          min-width="120"
          align="left"
          fixed="right"
        >
          <template slot-scope="{row}">
            <el-button type="text" @click="$refs.operaJkSketchWindow.open('线路优化', row)" icon="el-icon-edit" v-permissions="['business:jksketch:update']">线路优化</el-button>
            <el-button type="text" @click="$refs.OperaJkSketchResultWindow.open('线路优化结果', row)" icon="el-icon-view" v-if="row.status ==2" >优化结果</el-button>
            <el-button type="text" @click="$refs.OperaJkSketchResultWindow.open('优化结果微调-', row)" icon="el-icon-edit" v-if="row.status ==2" >优化结果微调</el-button>
          </template>
        </el-table-column>
      </el-table>
@@ -76,7 +79,7 @@
    <OperaJkSketchWindow ref="operaJkSketchWindow" @success="handlePageChange"/>
    <OperaJkSketchResultWindow ref="OperaJkSketchResultWindow" @success="handlePageChange"/>
    <OperaJkSketchImportWindow ref="OperaJkSketchImportWindow" @success="handlePageChange"/>
    <OperaJkSketchLineListWindow ref="operaJkSketchLineListWindow"  />
    <OperaJkSketchLineListWindow ref="operaJkSketchLineListWindow"  @success="handlePageChange" />
  </TableLayout>
</template>
server/pom.xml
@@ -42,7 +42,13 @@
        <type>pom</type>
        <scope>import</scope>
      </dependency>
        <dependency>
          <groupId>com.google.protobuf</groupId>
          <artifactId>protobuf-java</artifactId>
          <version>4.31.1</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      <!-- Alibaba Cloud ä¾èµ–管理 -->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
server/system_service/pom.xml
@@ -21,7 +21,7 @@
        <mybatis.plus.version>3.5.7</mybatis.plus.version>
        <apache.shiro.version>1.7.0</apache.shiro.version>
        <oshi.version>5.7.0</oshi.version>
        <jna.version>5.7.0</jna.version>
        <jna.version>5.14.0</jna.version>
        <poi.version>5.0.0</poi.version>
        <spring-cloud.version>2021.0.3</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
@@ -36,7 +36,6 @@
        <!-- é˜¿é‡Œäº‘OSS存储 -->
        <aliyun-oss.version>3.8.0</aliyun-oss.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
@@ -185,16 +184,16 @@
            <groupId>com.github.oshi</groupId>
            <artifactId>oshi-core</artifactId>
            <version>${oshi.version}</version>
        </dependency>
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>${jna.version}</version>
        </dependency>
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna-platform</artifactId>
            <version>${jna.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>jna-platform</artifactId>
                    <groupId>net.java.dev.jna</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>jna</artifactId>
                    <groupId>net.java.dev.jna</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.yulichang/mybatis-plus-join -->
        <dependency>
@@ -316,7 +315,6 @@
                <type>pom</type>
                <scope>import</scope>
            </dependency>
       <!-- <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
@@ -340,11 +338,40 @@
            <type>pom</type>
            <scope>import</scope>
        </dependency>-->
         <dependency>
    <!--    <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>4.31.1</version>
        </dependency>
-->
        <!-- <dependency>
            <groupId>com.google.ortools</groupId>
            <artifactId>ortools-java</artifactId>
            <version>9.14.6206</version>
             <exclusions>
                 <exclusion>
                     <groupId>com.google.protobuf</groupId>
                     <artifactId>protobuf-java</artifactId>
                 </exclusion>
             </exclusions>
        </dependency>
-->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>4.31.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.google.ortools</groupId>
            <artifactId>ortools-java</artifactId>
            <version>9.14.6206</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.protobuf</groupId>
                    <artifactId>protobuf-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>
server/system_service/src/main/java/com/doumee/core/utils/Constants.java
@@ -161,6 +161,7 @@
    public static final String LIQUID_LEVEL_UNIT ="LIQUID_LEVEL_UNIT" ;
    public static final String OUT_HY_LOT_TOTAL ="OUT_HY_LOT_TOTAL" ;
    public static final String BANNER_IMG ="BANNER_IMG" ;
    public static final String COMPANY_LOCATION = "COMPANY_LOCATION";
    public static final String GAODE_LOCATION_GEOAPI_URL = "GAODE_LOCATION_GEOAPI_URL";
    public static  boolean DEALING_HK_SYNCPRIVILEGE= false;
@@ -520,6 +521,7 @@
    public interface RedisKeys {
        public static final String IMPORTING_CARS ="IMPORTING_CARS";
        public static final String CHECKING_JKCUSTOMER_LOCATION ="CHECKING_JKCUSTOMER_LOCATION";
        public static final String JKLINE_JOB ="JKLINE_JOB_";
        public static final String IMPORTING_JKCUSTOMER ="IMPORTING_JKCUSTOMER";
        public static final String IMPORTING_JKORDERS ="IMPORTING_JKORDERS";
        public static final String IMPORTING_JKLINE ="IMPORTING_JKLINE";
server/system_service/src/main/java/com/doumee/core/utils/DateUtil.java
@@ -3120,6 +3120,25 @@
    }
    /**
     * èŽ·å–å½“å‰æ—¥æœŸæ˜¯æ˜ŸæœŸå‡ <br>
     *
     * @param dt
     * @return å½“前日期是星期几
     */
    public static String getWeekZhouOfDate(Date dt) {
        String[] weekDays = { "周日", "周一", "周二", "周三", "周四", "周五", "周六" };
        Calendar cal = Calendar.getInstance();
        cal.setTime(dt);
        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
        if (w < 0)
        {
            w = 0;
        }
        return weekDays[w];
    }
    /**
     * èŽ·å–æ˜ŸæœŸæ•°
     *
     * @param dt
server/system_service/src/main/java/com/doumee/core/utils/DistanceCalculator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.doumee.core.utils;
public class DistanceCalculator {
    private static final double EARTH_RADIUS = 6371.0; // åœ°çƒåŠå¾„(单位:公里)
    public static void main(String[] args) {
        //118.363428,31.326485
        //118.363312,31.326638
        System.out.println(calculateDistance(39.326638d,116.363312d,31.326606,118.363272));
    }
    public static long calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                        Math.sin(dLon / 2) * Math.sin(dLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return  (long) (EARTH_RADIUS * c * 1000);
    }
}
server/system_service/src/main/java/com/doumee/core/utils/TspSolver.java
ÎļþÒÑɾ³ý
server/system_service/src/main/java/com/doumee/core/utils/tsp/TspSolver.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,280 @@
package com.doumee.core.utils.tsp;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.google.ortools.Loader;
import com.google.ortools.constraintsolver.*;
import lombok.extern.slf4j.Slf4j;
import com.google.protobuf.Internal.IntListAdapter.IntConverter;
import  com.google.ortools.constraintsolver.mainJNI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class TspSolver {
    public static void main(String[] args) throws Exception {
        DataModel data = new DataModel();
        data.initDataList();//构造数据
        startSearch(data);
        /*// åˆå§‹åŒ–数据模型
        Loader.loadNativeLibraries();
        DataModel data = new DataModel();
        data.initDataList();//构造数据
        long start =System.currentTimeMillis();
        System.out.println("=============start=========="+start);
        //创建求解器manager对象,初始化求解器数据
        RoutingIndexManager manager =  new RoutingIndexManager(data.distanceMatrix.length, data.vehicleNumber, data.depot);
        // åˆå§‹åŒ–求解器
        RoutingModel routing = new RoutingModel(manager);
//        // æ³¨å†Œå›žè°ƒå‡½æ•°
        final int transitCallbackIndex =
                routing.registerTransitCallback((long fromIndex, long toIndex) -> {
                    int fromNode = manager.indexToNode(fromIndex);
                    int toNode = manager.indexToNode(toIndex);
                    return data.distanceMatrix[fromNode][toNode];
                });
        // å®šä¹‰å›žè°ƒå‡½æ•°è‡³æ¯æ¡è·¯çº¿
        routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex);
        // å¢žåŠ è·ç¦»ç»´åº¦çº¦æŸ
        routing.addDimension(transitCallbackIndex, 0, 30000,
                true,
                "Distance");
        RoutingDimension distanceDimension = routing.getMutableDimension("Distance");
        distanceDimension.setGlobalSpanCostCoefficient(100);
//        // æ·»åŠ å®¹é‡é™åˆ¶
        final int demandCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> {
            int fromNode = manager.indexToNode(fromIndex);
            return data.demands[fromNode];
        });
        routing.addDimensionWithVehicleCapacity(demandCallbackIndex, 0, data.vehicleCapacities, true, "Capacity");
        Solver solver = routing.solver();
        //设置搜索方法
        RoutingSearchParameters searchParameters =
                main.defaultRoutingSearchParameters()
                        .toBuilder()
                        .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC)
                        .build();
        // æ‰§è¡Œç®—法
        Assignment solution = routing.solveWithParameters(searchParameters);
        // æ‰“印路线
        printSolution(data, routing, manager, solution);
        long end =System.currentTimeMillis();
        System.out.println("=============end=========="+end);
        System.out.println("=============耗时=========="+(end -start)+"(ms)"+(end -start)/1000 +"s"+(end -start)/60/1000 +"m");
    */}
    public static void startSearch(DataModel data) {
        // åˆå§‹åŒ–数据模型
        Loader.loadNativeLibraries();
        long start =System.currentTimeMillis();
        System.out.println("=============start=========="+start);
        //创建求解器manager对象,初始化求解器数据
        RoutingIndexManager manager =  new RoutingIndexManager(data.distanceMatrix.length, data.vehicleNumber, data.depot);
        // åˆå§‹åŒ–求解器
        RoutingModel routing = new RoutingModel(manager);
        // æ³¨å†Œå›žè°ƒå‡½æ•°
        final int transitCallbackIndex =
                routing.registerTransitCallback((long fromIndex, long toIndex) -> {
                    int fromNode = manager.indexToNode(fromIndex);
                    int toNode = manager.indexToNode(toIndex);
                    return data.distanceMatrix[fromNode][toNode];
                });
        // å®šä¹‰å›žè°ƒå‡½æ•°è‡³æ¯æ¡è·¯çº¿
        routing.setArcCostEvaluatorOfAllVehicles(transitCallbackIndex);
        // å¢žåŠ è·ç¦»ç»´åº¦çº¦æŸ
        routing.addDimension(transitCallbackIndex, 0, 300000000,
                true,
                "Distance");
        RoutingDimension distanceDimension = routing.getMutableDimension("Distance");
        distanceDimension.setGlobalSpanCostCoefficient(100);
//        // æ·»åŠ å®¹é‡é™åˆ¶
        final int demandCallbackIndex = routing.registerUnaryTransitCallback((long fromIndex) -> {
            int fromNode = manager.indexToNode(fromIndex);
            return data.demands[fromNode];
        });
        routing.addDimensionWithVehicleCapacity(demandCallbackIndex, 0, data.vehicleCapacities, true, "Capacity");
//        routing.addDimensionWithVehicleTransits()
        Solver solver = routing.solver();
        //设置搜索方法
        RoutingSearchParameters searchParameters =
                main.defaultRoutingSearchParameters()
                        .toBuilder()
                        .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC)
                        .build();
        // æ‰§è¡Œç®—法
        Assignment solution = routing.solveWithParameters(searchParameters);
        if(solution ==null){
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"未找到最优路线!");
        }
        // æ‰“印路线
        resultSolution(data, routing, manager, solution);
        long end =System.currentTimeMillis();
        System.out.println("=============end=========="+end);
        System.out.println("=============耗时=========="+(end -start)+"(ms)"+(end -start)/1000 +"s"+(end -start)/60/1000 +"m");
    }
    static void resultSolution( DataModel data, RoutingModel routing, RoutingIndexManager manager, Assignment solution) {
        long maxRouteDistance = 0;
         List<TspSolverSolutions> mapList = new ArrayList<>();
        for (int i = 0; i < data.vehicleNumber; ++i) {
            TspSolverSolutions t = new TspSolverSolutions();
            t.setLineIndex(i);
            long index = routing.start(i);
            log.info("Route for Vehicle " + i + ":");
            long routeDistance = 0;
            int routeDemand = 0;
            String route = "";
            List<Integer> routeList = new ArrayList<>();
            while (!routing.isEnd(index)) {
                int tNode = manager.indexToNode(index);
                routeList.add( tNode);
                route += tNode+ " -> ";
                routeDemand += data.demands[tNode];
                long previousIndex = index;
                index = solution.value(routing.nextVar(index));
                routeDistance += routing.getArcCostForVehicle(previousIndex, index, i);
            }
            int tNode = manager.indexToNode(index);
            routeList.add( tNode);
            log.info(route + tNode);
            t.setRouteIndex(routeList);
            t.setDistance(routeDistance);
            log.info("Distance of the route: " + routeDistance + "m"+"  Capacity of the route:"+routeDemand+"/"+data.vehicleCapacities[i]);
            maxRouteDistance = Math.max(routeDistance, maxRouteDistance);
            mapList.add(t);
        }
        data.setSolutions(mapList);
        log.info("Maximum of the route distances: " + maxRouteDistance + "m");
    }
    static void printSolution( DataModel data, RoutingModel routing, RoutingIndexManager manager, Assignment solution) {
        long maxRouteDistance = 0;
        for (int i = 0; i < data.vehicleNumber; ++i) {
            long index = routing.start(i);
            log.info("Route for Vehicle " + i + ":");
            long routeDistance = 0;
            int routeDemand = 0;
            String route = "";
            List<Integer> routeList = new ArrayList<>();
            while (!routing.isEnd(index)) {
                int tNode = manager.indexToNode(index);
                routeList.add( tNode);
                route += tNode+ " -> ";
                routeDemand += data.demands[tNode];
                long previousIndex = index;
                index = solution.value(routing.nextVar(index));
                routeDistance += routing.getArcCostForVehicle(previousIndex, index, i);
            }
            log.info(route + manager.indexToNode(index));
            log.info("Distance of the route: " + routeDistance + "m"+"  Capacity of the route:"+routeDemand+"/"+data.vehicleCapacities[i]);
            maxRouteDistance = Math.max(routeDistance, maxRouteDistance);
        }
        log.info("Maximum of the route distances: " + maxRouteDistance + "m");
    }
    public static void startOptimize(){
    }
   public static class DataModel {
        //距离矩阵
        public int lenght;
        //最大车辆限制
        public  int vehicleNumber;
        public List<TspSolverSolutions> solutions;
        //起点
        public static final int depot = 0;
        //每一个点的商品的数量
        public   long[] demands;
        //车辆最大容载
        public   long[] vehicleCapacities ;
        public   long[][] distanceMatrix ;
       public List<TspSolverSolutions> getSolutions() {
           return solutions;
       }
       public void setSolutions(List<TspSolverSolutions> solutions) {
           this.solutions = solutions;
       }
       public  void initDataInfo(int vehicleNumber1, long[] demands1, long[] vehicleCapacities1, long[][] distanceMatrix1){
            this.demands = demands1;
            this.vehicleNumber = vehicleNumber1;
            this.vehicleCapacities=vehicleCapacities1;
            this.distanceMatrix=distanceMatrix1;
        }
        public   void initDataList(){
            lenght = 100;
            vehicleNumber = 7;
            demands = new long[lenght];
            vehicleCapacities =new long[vehicleNumber];
            distanceMatrix =  new long[lenght][lenght];
            int total0 =0;
            for (int i = 0; i <vehicleNumber ; i++) {
                long tem = (long) (Math.random() * 1000 + 20000);
                vehicleCapacities[i] = tem;
                total0+=tem;
                System.out.print(tem+" ,");
            }
            System.out.println( "\ntotal Capacity:"+total0+"=====================");
            long total = 0;
            for (int i = 0; i <lenght ; i++) {
                long tem =  (int)(Math.random()*100+100);
                demands[i] =tem;
                total+=tem;
                System.out.print(tem+" ,");
                for (int j = 0; j <lenght ; j++) {
                    if(i == j){
                        distanceMatrix[i][j] =0;
                    }
                    if(i<j){
                        distanceMatrix[i][j] =(int)(Math.random()*1000+1);
                        distanceMatrix[j][i] =    distanceMatrix[i][j];
                    }
                }
            }
            System.out.println( "\ntotal Demands:"+total+"=====================");
        }
       /* public final long[][] distanceMatrix = {
                {0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662},
                {548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210},
                {776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754},
                {696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358},
                {582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244},
                {274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708},
                {502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480},
                {194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856},
                {308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514},
                {194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468},
                {536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354},
                {502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844},
                {388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730},
                {354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536},
                {468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194},
                {776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798},
                {662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0},
        };*/
    }
}
server/system_service/src/main/java/com/doumee/core/utils/tsp/TspSolverSolutions.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.doumee.core.utils.tsp;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.google.ortools.Loader;
import com.google.ortools.constraintsolver.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Data
public class TspSolverSolutions {
     private int lineIndex;
     private List<Integer> routeIndex;
     private long distance;
}
server/visits/dmvisit_admin/pom.xml
@@ -20,6 +20,18 @@
            <groupId>com.doumee</groupId>
            <artifactId>dmvisit_service</artifactId>
            <version>1.0.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>com.google.protobuf</groupId>
                    <artifactId>protobuf-java</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>4.31.1</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/JkSketchCloudController.java
@@ -66,7 +66,9 @@
    @PostMapping("/updateById")
    @CloudRequiredPermission("business:jksketch:update")
    public ApiResponse updateById(@RequestBody JkSketch jkSketch, @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
        jkSketchService.updateById(jkSketch);
        jkSketch.setLoginUserInfo(this.getLoginUser(token));
        JkSketch model = jkSketchService.updateById(jkSketch);
        jkSketchService.startUpdateLineAsync(model);//异步优化
        return ApiResponse.success(null);
    }
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/JkSketchCustomerCloudController.java
@@ -75,6 +75,13 @@
        return ApiResponse.success(jkSketchCustomerService.findPage(pageWrap));
    }
    @ApiOperation("列表查询")
    @PostMapping("/allList")
    @CloudRequiredPermission("business:jksketchcustomer:query")
    public ApiResponse<List<JkSketchCustomer>> allList (@RequestBody JkSketchCustomer pageWrap) {
        return ApiResponse.success(jkSketchCustomerService.findList(pageWrap));
    }
    @ApiOperation("导出Excel")
    @PostMapping("/exportExcel")
    @CloudRequiredPermission("business:jksketchcustomer:exportExcel")
server/visits/dmvisit_service/pom.xml
@@ -21,7 +21,7 @@
            <groupId>com.doumee</groupId>
            <artifactId>system_service</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
         </dependency>
        <dependency>
            <groupId>com.doumee</groupId>
            <artifactId>emaysms</artifactId>
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkLine.java
@@ -97,6 +97,9 @@
    @ExcelColumn(name="所属主线路",index = 2,width = 10)
    @TableField(exist = false)
    private String categoryName;
    @ApiModelProperty(value = "司机", example = "1")
    @TableField(exist = false)
    private String memberName;
}
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkOrders.java
@@ -13,6 +13,8 @@
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * äº¤æŽ§-销售订单信息表
@@ -83,6 +85,9 @@
    @ApiModelProperty(value = "同班组间客户位置距离数组,[{a:12,b:100},{a:13,b:200},...],a:客户编码,b:与客户a之间的距离")
    //@ExcelColumn(name="同班组间客户位置距离数组,[{a:12,b:100},{a:13,b:200},...],a:客户编码,b:与客户a之间的距离")
    private String distances;
    @ApiModelProperty(value = "同班组间客户位置距离数组,[{a:12,b:100},{a:13,b:200},...],a:客户编码,b:与客户a之间的距离")
    @TableField(exist = false)
    private List<Map<String,Object>> distanceList;
    @ApiModelProperty(value = "客户姓名", example = "1")
    @ExcelColumn(name="客户姓名",index = 2,width = 10)
@@ -104,5 +109,13 @@
    //@ExcelColumn(name="所属主线路",index = 2,width = 10)
    @TableField(exist = false)
    private Integer categoryId;
    @ApiModelProperty(value = "经度", example = "1")
    //@ExcelColumn(name="经度")
    @TableField(exist = false)
    private BigDecimal longitude;
    @ApiModelProperty(value = "维度", example = "1")
    //@ExcelColumn(name="维度")
    @TableField(exist = false)
    private BigDecimal latitude;
}
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkSketch.java
@@ -13,6 +13,7 @@
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
 * äº¤æŽ§-线路优化记录信息表
@@ -64,6 +65,17 @@
    @ApiModelProperty(value = "最新优化线路数", example = "1")
    //@ExcelColumn(name="主线路编码(关联category)")
    private Integer planLineNum;
    @ApiModelProperty(value = "优化结果", example = "1")
    //@ExcelColumn(name="主线路编码(关联category)")
    private String planLineInfo;
    @ApiModelProperty(value = "优化时间", example = "1")
    //@ExcelColumn(name="主线路编码(关联category)")
    private Date planLineDate;
    @ApiModelProperty(value = "优化结束时间", example = "1")
    private Date planLineEndDate;
    @ApiModelProperty(value = "优化人员", example = "1")
    //@ExcelColumn(name="主线路编码(关联category)")
    private Integer planLineUserId;
    @ApiModelProperty(value = "送货量", example = "1")
    @ExcelColumn(name="送货量(条)",index = 4,width = 10)
@@ -73,8 +85,8 @@
    @ExcelColumn(name="客户数(户)",index = 3,width = 7)
    private Integer orderNum;
    @ApiModelProperty(value = "状态 0未优化 1优化中 2已优化", example = "1")
    @ExcelColumn(name="状态",index = 5,width = 10,valueMapping = "0=未优化;1=优化中;2=已优化;")
    @ApiModelProperty(value = "状态 0未优化 1优化中 2已优化 3优化失败", example = "1")
    @ExcelColumn(name="状态",index = 5,width = 10,valueMapping = "0=未优化;1=优化中;2=已优化;3=优化失败;")
    private Integer status;
    @ApiModelProperty(value = "排序码", example = "1")
@@ -83,15 +95,31 @@
    @ApiModelProperty(value = "优化记录编码(作为key值存redis)", example = "1")
    //@ExcelColumn(name="班组编码(关联category)")
    private String jobId;
    @ApiModelProperty(value = "优化里程数(米)", example = "1")
    private Long distance;
    @ApiModelProperty(value = "原始里程数(米)", example = "1")
    private Long originDistance;
    @ApiModelProperty(value = "日期")
    @ExcelColumn(name="送货日期",index = 1,width = 10)
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date dateInfo;
    @ApiModelProperty(value = "所属主线路", example = "1")
    @ExcelColumn(name="主线路",index = 2,width = 10)
    @TableField(exist = false)
    private String categoryName;
    @ApiModelProperty(value = "是否强制优化 0否 1是", example = "1")
    @TableField(exist = false)
    private Integer forceUpdate;
    @ApiModelProperty(value = "线路客户订单信息", example = "1")
    @TableField(exist = false)
    List<JkSketchCustomer> customerList;
    @ApiModelProperty(value = "线路集合", example = "1")
    @TableField(exist = false)
    List<JkLine> lineList;
    @ApiModelProperty(value = "线路编码集合", example = "1")
    @TableField(exist = false)
    List<Integer> lineIdList;
}
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkSketchCustomer.java
@@ -13,6 +13,8 @@
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 * äº¤æŽ§-线路优化线路客户记录信息表
@@ -104,4 +106,19 @@
    //@ExcelColumn(name="所属主线路",index = 2,width = 10)
    @TableField(exist = false)
    private Integer categoryId;
    @ApiModelProperty(value = "经度", example = "1")
    //@ExcelColumn(name="经度")
    @TableField(exist = false)
    private BigDecimal longitude;
    @ApiModelProperty(value = "所在位置")
    @TableField(exist = false)
    private String location;
    @ApiModelProperty(value = "维度", example = "1")
    //@ExcelColumn(name="维度")
    @TableField(exist = false)
    private BigDecimal latitude;
    @ApiModelProperty(value = "同班组间客户位置距离数组,[{a:12,b:100},{a:13,b:200},...],a:客户编码,b:与客户a之间的距离")
    @TableField(exist = false)
    private List<Map<String,Object>> distanceList;
}
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/JkSketchLine.java
@@ -13,6 +13,7 @@
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
 * äº¤æŽ§-线路优化线路记录信息表
@@ -58,6 +59,9 @@
    @ApiModelProperty(value = "线路编码(关联jk_line)", example = "1")
    @ExcelColumn(name="线路编码(关联jk_line)")
    private Integer lineId;
    @ApiModelProperty(value = "路程数", example = "1")
    @ExcelColumn(name="路程数")
    private Long distance;
    @ApiModelProperty(value = "线路优化编码(关联jk_sketch)", example = "1")
    @ExcelColumn(name="线路优化编码(关联jk_sketch)")
@@ -96,5 +100,16 @@
    @ApiModelProperty(value = "所属主线路编码", example = "1")
    @TableField(exist = false)
    private Integer categoryId;
    @ApiModelProperty(value = "客户明细", example = "1")
    @TableField(exist = false)
    private List<JkSketchCustomer> customerList;
    @ApiModelProperty(value = "最大客户量", example = "1")
    @TableField(exist = false)
    private Integer maxCustomer;
    @ApiModelProperty(value = "最大订单量", example = "1")
    @TableField(exist = false)
    private Integer maxOrder;
}
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/JkSketchService.java
@@ -49,7 +49,7 @@
     *
     * @param jkSketch å®žä½“对象
     */
    void updateById(JkSketch jkSketch);
    JkSketch updateById(JkSketch jkSketch );
    /**
     * æ‰¹é‡ä¸»é”®æ›´æ–°
@@ -99,4 +99,6 @@
    long count(JkSketch jkSketch);
    List<JkSketch> importBatch(MultipartFile file, String dateInfo, LoginUserInfo loginUser);
    void startUpdateLineAsync(JkSketch model);
}
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkCustomerServiceImpl.java
@@ -271,6 +271,7 @@
                            );
                        }
                    }else{
                        log.error("更新交控中心客户经纬度信息=====获取json=========="+json);
                        log.error("更新交控中心客户经纬度信息=====获取失败=========="+c.getName()+"-"+c.getLocation());
                    }
                }catch (Exception e){
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkLineServiceImpl.java
@@ -147,10 +147,32 @@
    @Override
    public List<JkLine> findList(JkLine jkLine) {
        MPJLambdaWrapper<JkLine> queryWrapper = new MPJLambdaWrapper<>();
        jkLine.setIsdeleted(Constants.ZERO);
        QueryWrapper<JkLine> wrapper = new QueryWrapper<>(jkLine);
        wrapper.lambda().orderByAsc(JkLine::getCode);
        return jkLineMapper.selectList(wrapper);
        queryWrapper.selectAll(JkLine.class )
                .selectAs(Member::getName,JkLine::getMemberName)
                .selectAs(Cars::getCode,JkLine::getCarCode)
                .leftJoin(Cars.class,Cars::getId,JkLine::getCarId )
                .leftJoin(Member.class,Member::getId,Cars::getMemberId );
        jkLine.setIsdeleted(Constants.ZERO);
        if (jkLine.getIsdeleted() != null) {
            queryWrapper.eq(JkLine::getIsdeleted, jkLine.getIsdeleted());
        }
        if (jkLine.getCategoryId() != null) {
            queryWrapper.eq(JkLine::getCategoryId, jkLine.getCategoryId());
        }
        if (jkLine.getCarId() != null) {
            queryWrapper.eq(JkLine::getCarId, jkLine.getCarId());
        }
        if (jkLine.getStatus() != null) {
            queryWrapper.eq(JkLine::getStatus, jkLine.getStatus());
        }
        if (jkLine.getSortnum() != null) {
            queryWrapper.eq(JkLine::getSortnum, jkLine.getSortnum());
        }
        queryWrapper.orderByAsc(JkLine::getCode);
        return jkLineMapper.selectJoinList(JkLine.class,queryWrapper);
    }
  
    @Override
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkSketchCustomerServiceImpl.java
@@ -84,8 +84,58 @@
    @Override
    public List<JkSketchCustomer> findList(JkSketchCustomer jkSketchCustomer) {
        QueryWrapper<JkSketchCustomer> wrapper = new QueryWrapper<>(jkSketchCustomer);
        return jkSketchCustomerMapper.selectList(wrapper);
        MPJLambdaWrapper<JkSketchCustomer> queryWrapper = new MPJLambdaWrapper<>();
        jkSketchCustomer.setIsdeleted(Constants.ZERO);
        queryWrapper.selectAll(JkSketchCustomer.class )
                .selectAs(JkCustomer::getName,JkSketchCustomer::getName)
                .selectAs(JkCustomer::getCode,JkSketchCustomer::getCode)
                .selectAs(JkCustomer::getLatitude,JkSketchCustomer::getLatitude)
                .selectAs(JkCustomer::getLongitude,JkSketchCustomer::getLongitude)
                .selectAs(JkCustomer::getLocation,JkSketchCustomer::getLocation)
                .leftJoin(JkCustomer.class,JkCustomer::getId,JkSketchCustomer::getCustomerId ) ;
        queryWrapper.like(jkSketchCustomer.getCode()!=null,JkCustomer::getCode,jkSketchCustomer.getCode());
        queryWrapper.like(jkSketchCustomer.getName()!=null,JkCustomer::getName,jkSketchCustomer.getName());
        if (jkSketchCustomer.getId() != null) {
            queryWrapper.eq(JkSketchCustomer::getId,jkSketchCustomer.getId());
        }
        if (jkSketchCustomer.getCreator() != null) {
            queryWrapper.eq(JkSketchCustomer::getCreator,jkSketchCustomer.getCreator());
        }
        if (jkSketchCustomer.getCreateDate() != null) {
            queryWrapper.ge(JkSketchCustomer::getCreateDate, Utils.Date.getStart(jkSketchCustomer.getCreateDate()));
            queryWrapper.le(JkSketchCustomer::getCreateDate, Utils.Date.getEnd(jkSketchCustomer.getCreateDate()));
        }
        if (jkSketchCustomer.getEditor() != null) {
            queryWrapper.eq(JkSketchCustomer::getEditor,jkSketchCustomer.getEditor());
        }
        if (jkSketchCustomer.getEditDate() != null) {
            queryWrapper.ge(JkSketchCustomer::getEditDate, Utils.Date.getStart(jkSketchCustomer.getEditDate()));
            queryWrapper.le(JkSketchCustomer::getEditDate, Utils.Date.getEnd(jkSketchCustomer.getEditDate()));
        }
        if (jkSketchCustomer.getIsdeleted() != null) {
            queryWrapper.eq(JkSketchCustomer::getIsdeleted,jkSketchCustomer.getIsdeleted());
        }
        if (jkSketchCustomer.getInfo() != null) {
            queryWrapper.eq(JkSketchCustomer::getInfo,jkSketchCustomer.getInfo());
        }
        if (jkSketchCustomer.getSketchLineId() != null) {
            queryWrapper.eq(JkSketchCustomer::getSketchLineId,jkSketchCustomer.getSketchLineId());
        }
        if (jkSketchCustomer.getSketchId() != null) {
            queryWrapper.eq(JkSketchCustomer::getSketchId,jkSketchCustomer.getSketchId());
        }
        if (jkSketchCustomer.getTotalNum() != null) {
            queryWrapper.eq(JkSketchCustomer::getTotalNum,jkSketchCustomer.getTotalNum());
        }
        if (jkSketchCustomer.getOrderId() != null) {
            queryWrapper.eq(JkSketchCustomer::getOrderId,jkSketchCustomer.getOrderId());
        }
        if (jkSketchCustomer.getSortnum() != null) {
            queryWrapper.eq(JkSketchCustomer::getSortnum,jkSketchCustomer.getSortnum());
        }
        queryWrapper.orderByAsc(JkSketchCustomer::getSketchLineId,JkSketchCustomer::getSortnum);
       return   jkSketchCustomerMapper.selectJoinList(JkSketchCustomer.class,queryWrapper);
    }
  
    @Override
@@ -102,10 +152,13 @@
                .selectAs(Category::getName,JkSketchCustomer::getCategoryName)
                .leftJoin(JkOrders.class,JkOrders::getId,JkSketchCustomer::getOrderId )
                .leftJoin(JkCustomer.class,JkCustomer::getId,JkSketchCustomer::getCustomerId )
                .leftJoin(JkLine.class,JkLine::getId,JkOrders::getLineId )
                .leftJoin(JkSketchLine.class,JkSketchLine::getId,JkSketchCustomer::getSketchLineId )
                .leftJoin(JkLine.class,JkLine::getId,JkSketchLine::getLineId )
                .leftJoin(Category.class,Category::getId,JkLine::getCategoryId );
        queryWrapper.eq( pageWrap.getModel().getCategoryId()!=null,JkLine::getCategoryId, pageWrap.getModel().getCategoryId());
        queryWrapper.eq( pageWrap.getModel().getDateInfo()!=null,JkSketchCustomer::getDateInfo, pageWrap.getModel().getDateInfo());
        queryWrapper.like( pageWrap.getModel().getCode()!=null,JkCustomer::getCode, pageWrap.getModel().getCode());
        queryWrapper.like( pageWrap.getModel().getName()!=null,JkCustomer::getName, pageWrap.getModel().getName());
        if (pageWrap.getModel().getId() != null) {
            queryWrapper.eq(JkSketchCustomer::getId, pageWrap.getModel().getId());
@@ -146,7 +199,7 @@
            queryWrapper.eq(JkSketchCustomer::getSortnum, pageWrap.getModel().getSortnum());
        }
        queryWrapper.orderByAsc(JkSketchCustomer::getSketchLineId);
        queryWrapper.orderByAsc(JkSketchCustomer::getSketchLineId,JkSketchCustomer::getSortnum);
        IPage<JkSketchCustomer> result = jkSketchCustomerMapper.selectJoinPage(page, JkSketchCustomer.class,queryWrapper);
        return PageData.from(result);
    }
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkSketchLineServiceImpl.java
@@ -89,12 +89,15 @@
                .selectAs(JkLine::getName,JkSketchLine::getLineName)
                .selectAs(Category::getId,JkSketchLine::getCategoryId)
                .selectAs(Category::getName,JkSketchLine::getCategoryName)
                .selectAs(JkLine::getMaxOrder,JkSketchLine::getMaxOrder)
                .selectAs(JkLine::getMaxCustomer,JkSketchLine::getMaxCustomer)
                .leftJoin(JkLine.class,JkLine::getId,JkOrders::getLineId )
                .leftJoin(Cars.class,Cars::getId,JkLine::getCarId )
                .leftJoin(Member.class,Member::getId,Cars::getMemberId )
                .leftJoin(Category.class,Category::getId,JkLine::getCategoryId );
        wrapper.eq( jkSketchLine.getSketchId()!=null,JkSketchLine::getSketchId, jkSketchLine.getSketchId())
                .eq(  JkSketchLine::getIsdeleted,Constants.ZERO);
                .eq(  JkSketchLine::getIsdeleted,Constants.ZERO)
                .orderByAsc(JkLine::getSortnum );
        return jkSketchLineMapper.selectJoinList(JkSketchLine.class,wrapper);
    }
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/JkSketchServiceImpl.java
@@ -4,16 +4,15 @@
import com.doumee.core.annotation.excel.ExcelImporter;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
import com.doumee.core.utils.Constants;
import com.doumee.core.utils.DateUtil;
import com.doumee.dao.admin.request.JkCustomerImport;
import com.doumee.core.utils.*;
import com.doumee.core.utils.tsp.TspSolver;
import com.doumee.core.utils.tsp.TspSolverSolutions;
import com.doumee.dao.admin.request.JkOrdersImport;
import com.doumee.dao.business.*;
import com.doumee.dao.business.model.*;
import com.doumee.service.business.third.model.LoginUserInfo;
import com.doumee.service.business.third.model.PageData;
import com.doumee.service.business.third.model.PageWrap;
import com.doumee.core.utils.Utils;
import com.doumee.service.business.JkSketchService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -21,17 +20,18 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -85,10 +85,301 @@
        }
        jkSketchMapper.deleteBatchIds(ids);
    }
    //线路优化
    @Override
    public JkSketch updateById(JkSketch jkSketch ) {
        JkSketch model = jkSketchMapper.selectById(jkSketch.getId());
        if(model == null ||Constants.equalsInteger(model.getIsdeleted(),Constants.ONE)){
            throw  new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        if( model.getDateInfo() ==  null){
            throw  new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"该线路日期信息不正确,不支持优化!");
        }
       if(Constants.equalsInteger(Constants.ZERO,jkSketch.getForceUpdate()) && Constants.equalsInteger(model.getStatus(),Constants.ONE)){
            throw  new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"该线路存在正在优化中,请耐心等待优化完成或者选择强制优化操作!");
        }
         if(  jkSketch.getLineIdList() ==null || jkSketch.getLineIdList().size()==0 ){
            throw  new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"请选择有效合理的线路进行优化操作!");
        }
        //当前所有线路(符合条件的线路)
        List<JkLine> lineList =  jkLineMapper.selectJoinList(JkLine.class,new MPJLambdaWrapper<JkLine>()
                .selectAll(JkLine.class)
                .eq(JkLine::getWeeks,DateUtil.getWeekZhouOfDate(model.getDateInfo()))//只能选择当前的线路
                .eq(JkLine::getIsdeleted,Constants.ZERO)
                .in(JkLine::getId,jkSketch.getLineIdList())
        );
        Integer totalCus = 0,totalNum =0;
        for(JkLine line :lineList){
            totalCus += Constants.formatIntegerNum(line.getMaxCustomer());//总客户量
            totalNum += Constants.formatIntegerNum(line.getMaxOrder());//总送货量
        }
        if( totalCus < Constants.formatIntegerNum(model.getOrderNum())  ){
            throw  new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"该线路订单客户数量超过了线路总客户量限制,无法进行优化!");
        }
        if( totalNum < Constants.formatBigdecimal(model.getTotalNum()).doubleValue()){
            throw  new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"该线路订单送货量超过了线路总送货量限制,无法进行优化!");
        }
        model.setLineList(lineList);//线路集合
        model.setCustomerList(checkJketchCustomerLocation(model));//订单集合
        model.setStatus(Constants.ONE);
        model.setPlanLineNum(lineList.size());
        model.setEditDate(new Date());
        model.setEditor(jkSketch.getLoginUserInfo().getId());
        model.setPlanLineDate(model.getEditDate());
        model.setPlanLineUserId(jkSketch.getLoginUserInfo().getId());
        model.setJobId(UUID.randomUUID().toString());
        redisTemplate.opsForValue().set(Constants.RedisKeys.JKLINE_JOB+model.getJobId(),true );//做异步处理
        jkSketchMapper.updateById(model);
        return model;
    }
    @Override
    public void updateById(JkSketch jkSketch) {
        jkSketchMapper.updateById(jkSketch);
    @Async
    public void startUpdateLineAsync(JkSketch model) {
        try {
            List<JkSketchCustomer> customerList = model.getCustomerList();
            List<JkLine> lineList = model.getLineList();
            TspSolver.DataModel dataModel = new TspSolver.DataModel();
            int vehicleNumber1 = lineList.size();//线路数量
            long[] vehicleCapacities1=new long[lineList.size()];//每辆车的最大订单量限制
            long[] demands1 = new long[customerList.size()+1]; //各个点的订单量
            long[][] distanceMatrix1 = new long[customerList.size()+1][customerList.size()+1];
            demands1[0] =0;//原点
            double cLatitude =0;
            double cLongitude =0;
            String location = systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.COMPANY_LOCATION).getCode();
            try {
                String[] ss = location.split(",");
                cLongitude = Double.parseDouble(ss[0]);
                cLatitude = Double.parseDouble(ss[1]);
            }catch (Exception e){
            }
            for (int i = 0; i < customerList.size(); i++) {
                distanceMatrix1[0][i] =  DistanceCalculator.calculateDistance(cLatitude,cLongitude,customerList.get(i).getLatitude().doubleValue(),customerList.get(i).getLongitude().doubleValue())/1000;
                distanceMatrix1[i][0] = distanceMatrix1[0][i];
                demands1[i+1] = Constants.formatBigdecimal( customerList.get(i).getTotalNum()).longValue();  //各个点的订单量
                List<Map<String,Object>> disList = customerList.get(i).getDistanceList();
                for (int j = 0; j< disList.size(); j++) {
                    if(disList.size()>j){
                        distanceMatrix1[i+1][j+1]  = (Long) disList.get(j).get("b")/1000;//构造距离矩阵
                    }else{
                        distanceMatrix1[i+1][j+1]  = 1l;
                    }
                }
            }
            for (int i = 0; i < lineList.size(); i++) {
                vehicleCapacities1[i] = lineList.get(i).getMaxOrder();//每辆车的最大订单量限制
            }
            //构造优化数据模型
            dataModel.initDataInfo(vehicleNumber1,demands1,vehicleCapacities1,distanceMatrix1);
            TspSolver.startSearch(dataModel);
            dealSearchSolution(model,dataModel);
        }catch (Exception e){
            e.printStackTrace();
            jkSketchMapper.update(null,new UpdateWrapper<JkSketch>().lambda()
                    .eq(JkSketch::getId,model.getId() )
                    .eq(JkSketch::getJobId,model.getJobId() )
                    .set(JkSketch::getPlanLineInfo,"最近一次线路优化失败!")
                    .set(JkSketch::getStatus,Constants.THREE)
                    .set(JkSketch::getPlanLineEndDate,new Date()));
        }
    }
    private void dealSearchSolution(JkSketch model, TspSolver.DataModel dataModel) {
        Date date = new Date();
        List<TspSolverSolutions> solutions = dataModel.getSolutions();
        List<JkSketchLine> sketchLineList = new ArrayList<>();
        List<JkSketchCustomer> sketchCustomerList  = new ArrayList<>();
        long totalDistance = 0l;
        if(solutions!=null && solutions.size()>0){
            for(TspSolverSolutions so : solutions){
                List<Integer> routes = so.getRouteIndex();
                if(routes.size() <=2) {
                    continue;//无客户的非有效路线
                }
                totalDistance+= so.getDistance();
                JkLine  line =model.getLineList().get(so.getLineIndex());
                JkSketchLine tModel =  new JkSketchLine();
                tModel.setSketchId(model.getId());
                tModel.setCreator(model.getEditor());
                tModel.setCreateDate(date);
                tModel.setLineId(line.getId());
                tModel.setTotalNum(new BigDecimal(0));
                tModel.setOrderNum(routes.size()-2);
                tModel.setDateInfo(model.getDateInfo());
                tModel.setSortnum(sketchLineList.size()+1);
                tModel.setEditDate(tModel.getCreateDate());
                tModel.setEditor(tModel.getCreator());
                tModel.setIsdeleted(Constants.ZERO);
                tModel.setDistance(so.getDistance());//
                sketchLineList.add(tModel);
                tModel.setCustomerList( new ArrayList<>());
                //有效路径
                for (Integer cIndex : routes){
                    if(cIndex ==0 || cIndex == routes.size()-1){
                        continue; //起始点不处理
                    }
                    JkSketchCustomer customer = model.getCustomerList().get(cIndex-1);
                    tModel.setTotalNum(tModel.getTotalNum().add(Constants.formatBigdecimal(customer.getTotalNum())));//送货量
                    JkSketchCustomer cModel =  new JkSketchCustomer();
                    cModel.setCreator(tModel.getCreator());
                    cModel.setOrderId(customer.getOrderId());
                    cModel.setCreateDate(tModel.getCreateDate());
                    cModel.setTotalNum(customer.getTotalNum());
                    cModel.setDateInfo(model.getDateInfo());
                    cModel.setSortnum(tModel.getCustomerList().size()+1);
                    cModel.setEditDate(tModel.getCreateDate());
                    cModel.setEditor(tModel.getCreator());
                    cModel.setIsdeleted(Constants.ZERO);
                    cModel.setSketchId(model.getId());
                    cModel.setCustomerId(customer.getCustomerId());
                    tModel.getCustomerList().add(cModel);
                    if(cIndex ==0 || cIndex == routes.size()-1){
                        continue; //起始点不处理
                    }
                }
            }
        }
        if(sketchLineList!=null && sketchLineList.size()>0){
            jkSketchLineMapper.update(null,new UpdateWrapper<JkSketchLine>().lambda()
                    .set(JkSketchLine::getIsdeleted,Constants.ONE)
                    .eq(JkSketchLine::getIsdeleted,Constants.ZERO)
                    .eq(JkSketchLine::getDateInfo,model.getDateInfo()));
            jkSketchCustomerMapper.update(null,new UpdateWrapper<JkSketchCustomer>().lambda()
                    .set(JkSketchCustomer::getIsdeleted,Constants.ONE)
                    .eq(JkSketchCustomer::getIsdeleted,Constants.ZERO)
                    .eq(JkSketchCustomer::getDateInfo,model.getDateInfo()));
            if(sketchLineList.size()>0){
                jkSketchLineMapper.insert(sketchLineList);
            }
            for(JkSketchLine l : sketchLineList){
                if(l.getCustomerList()!=null ){
                    for(JkSketchCustomer c :l.getCustomerList()){
                        c.setSketchLineId(l.getId());
                    }
                    sketchCustomerList.addAll(l.getCustomerList());
                }
            }
            if(sketchCustomerList.size()>0){
                jkSketchCustomerMapper.insert(sketchCustomerList);
            }
            jkSketchMapper.update(null,new UpdateWrapper<JkSketch>().lambda()
                    .eq(JkSketch::getId,model.getId() )
                    .eq(JkSketch::getJobId,model.getJobId() )
                    .set(JkSketch::getLineNum,sketchLineList.size() )
                    .set(JkSketch::getDistance,totalDistance)
                    .set(JkSketch::getPlanLineInfo,"最近一次线路优化成功,优化后总距离:"+(totalDistance/1000)+"公里!")
                    .set(JkSketch::getStatus,Constants.TWO)
                    .set(JkSketch::getPlanLineEndDate,date));
        }else{
            jkSketchMapper.update(null,new UpdateWrapper<JkSketch>().lambda()
                    .eq(JkSketch::getId,model.getId() )
                    .eq(JkSketch::getJobId,model.getJobId() )
                    .set(JkSketch::getPlanLineInfo,"最近一次线路优化失败,未找到最优路线!")
                    .set(JkSketch::getStatus,Constants.THREE)
                    .set(JkSketch::getPlanLineEndDate,date));
        }
    }
    private   List<JkSketchCustomer>  checkJketchCustomerLocation(JkSketch model) {
        MPJLambdaWrapper<JkSketchCustomer> queryWrapper = new MPJLambdaWrapper<>();
        queryWrapper.selectAll(JkSketchCustomer.class )
                .selectAs(JkCustomer::getName,JkSketchCustomer::getName)
                .selectAs(JkCustomer::getCode,JkSketchCustomer::getCode)
                .selectAs(JkCustomer::getLongitude,JkSketchCustomer::getLongitude)
                .selectAs(JkCustomer::getLatitude,JkSketchCustomer::getLatitude)
                .leftJoin(JkCustomer.class,JkCustomer::getId,JkSketchCustomer::getCustomerId )
                .eq(JkSketchCustomer::getSketchId, model.getId())
                .eq(JkSketchCustomer::getIsdeleted,Constants.ZERO)
                .orderByAsc(JkSketchCustomer::getSortnum);
        List<JkSketchCustomer> customerList = jkSketchCustomerMapper.selectJoinList(JkSketchCustomer.class,queryWrapper);
        if(customerList == null ||customerList.size() ==0){
            throw  new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"该线路客户信息为空,不满足优化条件!");
        }
        String errorMsg ="";
        for(JkSketchCustomer c : customerList){
            if(c.getLatitude()==null || c.getLongitude() ==null){
                errorMsg += c.getName()+"-"+c.getName()+";";
            }
        }
        if(StringUtils.isNotBlank(errorMsg)){
            throw  new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"该线路客户:"+errorMsg+"定位信息不完整,不满足优化条件!");
        }
        for(JkSketchCustomer c : customerList){
            List<Map<String,Object>> tmpList = new ArrayList<>();
            for(JkSketchCustomer cm : customerList){
                Map<String,Object> t = new HashMap<>();
                t.put("a",cm.getId());
                if(Constants.equalsInteger(c.getCustomerId(),cm.getCustomerId())){
                    t.put("b",0l);
                }else{
                    t.put("b", DistanceCalculator.calculateDistance(
                            Constants.formatBigdecimal(c.getLatitude()).doubleValue(),
                            Constants.formatBigdecimal(c.getLatitude()).doubleValue(),
                            Constants.formatBigdecimal(cm.getLatitude()).doubleValue(),
                            Constants.formatBigdecimal(cm.getLongitude()).doubleValue()));
                }
                tmpList.add(t);
            }
            c.setDistanceList(tmpList);
        }
        if(Constants.equalsInteger(Constants.ZERO,model.getStatus())){
            //如果是未优化状态,计算原始距离
            long totalDistance = 0;
            MPJLambdaWrapper<JkSketchLine> queryWrapper1 = new MPJLambdaWrapper<>();
            queryWrapper1.selectAll(JkSketchLine.class )
                    .eq(JkSketchLine::getSketchId, model.getId())
                    .eq(JkSketchLine::getIsdeleted,Constants.ZERO);
            List<JkSketchLine> lineList = jkSketchLineMapper.selectJoinList(JkSketchLine.class,queryWrapper1);//
            if(lineList!=null ||lineList.size()>0){
                double cLatitude =0;
                double cLongitude =0;
                String location = systemDictDataBiz.queryByCode(Constants.SYSTEM,Constants.COMPANY_LOCATION).getCode();
                try {
                    String[] ss = location.split(",");
                    cLongitude = Double.parseDouble(ss[0]);
                    cLatitude = Double.parseDouble(ss[1]);
                }catch (Exception e){
                }
                for(JkSketchLine line : lineList){
                    boolean isFirst =true;
                    JkSketchCustomer last = null;
                    for(JkSketchCustomer c : customerList){
                        if(Constants.equalsInteger(c.getSketchLineId(),line.getId())){
                            if(isFirst){
                                totalDistance += DistanceCalculator.calculateDistance(cLatitude,cLongitude
                                        ,Constants.formatBigdecimal(c.getLatitude()).doubleValue()
                                        ,Constants.formatBigdecimal(c.getLongitude()).doubleValue());
                            }else{
                                totalDistance += DistanceCalculator.calculateDistance(Constants.formatBigdecimal(c.getLatitude()).doubleValue()
                                        ,Constants.formatBigdecimal(c.getLongitude()).doubleValue()
                                        ,Constants.formatBigdecimal(last.getLatitude()).doubleValue()
                                        ,Constants.formatBigdecimal(last.getLongitude()).doubleValue());
                            }
                            last = c;
                            isFirst=false;
                        }
                        if(last!=null){
                            totalDistance += DistanceCalculator.calculateDistance(cLatitude,cLongitude
                                    ,Constants.formatBigdecimal(last.getLatitude()).doubleValue()
                                    ,Constants.formatBigdecimal(last.getLongitude()).doubleValue());
                        }
                    }
                }
            }
            model.setOriginDistance(totalDistance);
        }
        return customerList;
    }
    @Override
@@ -185,6 +476,7 @@
        return jkSketchMapper.selectCount(wrapper);
    }
    @Override
    @Transactional(rollbackFor = {Exception.class,BusinessException.class})
    public List<JkSketch> importBatch(MultipartFile file, String dateInfoStr, LoginUserInfo loginUser){
@@ -216,9 +508,11 @@
                    .selectAll(JkCustomer.class)
                    .eq(JkCustomer::getIsdeleted,Constants.ZERO)
            );
            String week =  DateUtil.getWeekZhouOfDate(dateInfo);
            //当前所有线路
            List<JkLine> lineList =  jkLineMapper.selectJoinList(JkLine.class,new MPJLambdaWrapper<JkLine>()
                    .selectAll(JkLine.class)
                    .eq(JkLine::getWeeks,week)//只能选择当前的线路
                    .eq(JkLine::getIsdeleted,Constants.ZERO)
            );
            List<JkOrders> newOrderList = new ArrayList<>();
@@ -256,7 +550,7 @@
                jkOrdersMapper.insert(newOrderList);
            }
            List<JkSketchLine> sketchLineList = initNewSketchLineList(newList,newOrderList);
            List<JkSketchLine> sketchLineList = initNewSketchLineList(newList,newOrderList,lineList);
            jkSketchLineMapper.update(null,new UpdateWrapper<JkSketchLine>().lambda()
                    .set(JkSketchLine::getIsdeleted,Constants.ONE)
                    .eq(JkSketchLine::getIsdeleted,Constants.ZERO)
@@ -285,6 +579,8 @@
    }
    private List<JkSketchCustomer> initNewSketchCustomerList(List<JkSketchLine> sketchLineList, List<JkOrders> newOrderList) {
        List<JkSketchCustomer> list =new ArrayList<>();
        for(JkOrders orders : newOrderList){
@@ -308,9 +604,25 @@
        }
        return list;
    }
    private List<JkSketchLine> initNewSketchLineList(List<JkSketch> newList, List<JkOrders> newOrderList) {
    private List<JkSketchLine> initNewSketchLineList(List<JkSketch> newList, List<JkOrders> newOrderList,List<JkLine> lineList) {
        List<JkSketchLine> list =new ArrayList<>();
        for(JkOrders orders : newOrderList){
        for (JkLine line : lineList){
            JkSketchLine tModel =  new JkSketchLine();
            tModel.setSketchId(getSKetchIdByyCategoryId(line.getCategoryId(),newList));
            tModel.setCreator(newList.get(0).getCreator());
            tModel.setCreateDate(newList.get(0).getCreateDate());
            tModel.setLineId(line.getId());
            initOrderAndTotalNum(tModel,line.getId(),newOrderList);
            tModel.setDateInfo(newList.get(0).getDateInfo());
            tModel.setSortnum(list.size()+1);
            tModel.setEditDate(tModel.getCreateDate());
            tModel.setEditor(tModel.getCreator());
            tModel.setIsdeleted(Constants.ZERO);
            if(tModel.getSketchId()!=null && Constants.formatIntegerNum(tModel.getOrderNum()) >0){
                list.add(tModel);
            }
        }
        /*for(JkOrders orders : newOrderList){
            JkSketchLine tModel = findModelFromListByLineId(orders.getLineId(),list);
            if(tModel!=null){
                tModel.setTotalNum(  tModel.getTotalNum().add(orders.getNum()));//订单量累计
@@ -332,8 +644,17 @@
                    list.add(tModel);
                }
            }
        }
        }*/
        return list;
    }
    private void initOrderAndTotalNum(JkSketchLine tModel, Integer id, List<JkOrders> newOrderList) {
        for(JkOrders orders : newOrderList){
            if(Constants.equalsInteger(orders.getLineId(),id)){
                tModel.setOrderNum(Constants.formatIntegerNum(tModel.getOrderNum())+1);
                tModel.setTotalNum(Constants.formatBigdecimal(tModel.getTotalNum()).add(Constants.formatBigdecimal(orders.getNum())));
            }
        }
    }
    private Integer getSKetchIdByyCategoryId(Integer categoryId, List<JkSketch> newList) {
@@ -430,7 +751,7 @@
        }
        JkLine line = findLineFromListByName(model.getLineName(),lineList);
        if(line == null){
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "对不起,第" + (index + 3) + "行线路【" + model.getLineName() + "】不存在,请检查表格内容!");
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "对不起,第" + (index + 3) + "行线路【" + model.getLineName() + "】不存在或者配送周期不符合,请检查表格内容!");
        }
        JkOrders tModel =   new JkOrders();
@@ -441,6 +762,8 @@
        tModel.setNum(getDecimalByVal(model.getNum()));
        tModel.setCustomerId(customer.getId());
        tModel.setDateInfo(dateInfo);
        tModel.setLatitude(customer.getLatitude());
        tModel.setLongitude(customer.getLongitude());
        tModel.setSortnum(model.getSortnum());
        tModel.setLineId(line.getId());
        tModel.setCategoryId(line.getCategoryId());