1. 取派模块业务分析
已经完成基础设置模块 : 收派标准管理、 取派员管理、 区域管理、分区管理、定区管理
基础设置模块的数据,实际上为后期业务模块提供支持
重点开发任务,业务受理模块
业务受理功能
受理环节,是宅急送业务的开始,作为服务前端,客户通过电话、网络等多种方式进行委托,业务受理员通过与客户交流,获取客户的服务需求和具体委托信息,将服务指令输入我司服务系统。
业务受理后,业务人员需要将客户信息,录入为 “业务通知单”(代表客户一份快递需要) ,系统进行自动分单,产生“工单” , 系统自动识别客户对应定区的负责人员, 由指定取派员到客户家取货
工作单功能 “工作单”
工作单基础信息的录入是信息的入口,并且也是本系统中人机交互最多的环节;基础信息录入的及时、准确、完整会直接影响后续的实物的运转配载、小件员交款、财务收入确认、成本垫付分摊,所以对数据的准确性、及时性、完整性要求比较高。
业务通知单: 客户快递申请
工单: 取派员取件任务
工作单 : 面向物流的数据,真实货物信息
查台转单
当自动下单成功,但是由于基础信息错误,而导致分配的取货人员错误,通过查台转单来解决此类失误。 (分错取派员,更换取派员 )
人工调度
在自动下单的时候,由于取件地址无法匹配取件人员,就转入人工调度。 (业务人员,手动为客户,指定取派员 )
2. 取派业务模块— 数据库设计
根据 “业务受理数据表.xls” 设计PowerDesigner PDM 物理数据模型
参考 “业务受理.pdm”
根据PDM生成SQL 脚本,将脚本导入 数据库
qp_noticebill 业务通知单
qp_workbill 工单
qp_workordermanage 工作单
根据数据表,使用MyEclipse 反转生成PO类和hbm映射
修改生成的类名
3. 业务受理功能
客户提交快递申请 ,业务人员将客户业务信息,录入到系统
3.1. 业务通知单页面编写
业务通知单录入页面 : /WEB-INF/pages/qupai/noticebill_add.jsp
页面存在大form 表单 ,使用EasyUI form 控件
validatebox (验证框) :非空、长度
<input type="text" class="easyui-validatebox" required="true" />
numberbox (数字框) :对validatebox扩展,只允许输入数字
<input type="text" class="easyui-numberbox" required="true" />
combobox (下拉框) : 可以对 <input> 元素,生成下拉列表
datebox (日历控件) : 选择日期
<input type="text" class="easyui-datebox" data-options="required:true,editable:false"/>
检查业务受理form表单,元素name属性和 NoticeBill 实体类是否一致
为form添加 点击提交事件
// 点击新单按钮,将业务通知单 保存
$('#save').click(function(){
if($("#noticebillForm").form('validate')){
$('#noticebillForm').submit();
}else{
$.messager.alert('警告','表单存在非法数据项!','warning');
}
});
3.2. 编写业务受理服务器代码
3.2.1. 完成基本代码结构
public class NoticeBillAction extends BaseAction implements ModelDriven<NoticeBill> {
}
public interface NoticeBillService {
}
public class NoticeBillServiceImpl extends BaseService implements NoticeBillService {
}
将DAO 注入Service
将Service 注入 Action
在业务受理时,产生业务通知单的数据,在保存到数据库的时候,需要进行自动分单的操作
1、 用客户取货地址通过远程调用,访问CRM,比较CRM系统中客户地址 ,查找客户对应定区,再通过定区获得 取派员信息
2、 如果不能在CRM中找到客户对应定区,使用当前发件人地址去匹配分区地址 ,就可以通过分区去管理定区,匹配到取派员
如果自动分单成功,需要在数据表 qp_workbill 生成一条工单记录 !
如果自动分单失败,进入人工调度环节
3.2.2. 自动分单 — 去CRM匹配客户的地址
修改CRM业务接口 CustomerService
// 根据 客户地址 查询 定区编码
public String findDecidedZoneIdByCustomerAddress(String address);
将修改后的CustomerService 接口,复制BOS系统 !!!
编写 CustomerServiceTest测试用例 ,完成接口调试
3.2.3. 自动分单 —- 匹配分区的关键字
<query name="Subarea.findByAddresskey">
<![CDATA[from Subarea where addresskey = ?]]>
</query>
public void saveNoticeBill(NoticeBill noticeBill) {
// 将业务通知单数据保存到数据库
noticeBillDAO.save(noticeBill);
// 自动分单
// 1 、使用当前取件地址,去查询CRM系统 定区编码
String decidedZoneId = customerService.findDecidedZoneIdByCustomerAddress(noticeBill.getPickaddress());
if (decidedZoneId == null) {
// 未查到
// 2、匹配分区 关键字
String[] addressArray = noticeBill.getPickaddress().split(" "); // 北京市海淀区 xxx路 1号楼
if (addressArray.length >= 2) {
String addresskey = addressArray[1]; // 取第二个元素 作为关键字
List<Subarea> list = subareaDAO.findByNamedQuery("Subarea.findByAddresskey",addresskey);
// 只匹配到唯一的一个分区,而且这个分区已经关联到定区
if (list.size() == 1 && list.get(0).getDecidedZone() != null) {
// 自动分单成功
// 查到 (自动分单成功)
DecidedZone decidedZone = list.get(0).getDecidedZone();
// 通知单
noticeBill.setStaff(decidedZone.getStaff());
noticeBill.setOrdertype("自动");
// 工单信息
WorkBill workBill = new WorkBill();
workBill.setNoticeBill(noticeBill);
workBill.setStaff(decidedZone.getStaff());
workBill.setType("新");
workBill.setPickstate("新单");
workBill.setBuildtime(new Timestamp(System.currentTimeMillis()));
workBill.setAttachbilltimes(0);
workBill.setRemark(noticeBill.getRemark());
workBillDAO.save(workBill);
} else {
// 人工调度
noticeBill.setOrdertype("人工");
}
} else {
// 人工调度
noticeBill.setOrdertype("人工");
}
} else {
// 查到 (自动分单成功)
DecidedZone decidedZone = decidedZoneDAO.findById(decidedZoneId);
// 通知单
noticeBill.setStaff(decidedZone.getStaff());
noticeBill.setOrdertype("自动");
// 工单信息
WorkBill workBill = new WorkBill();
workBill.setNoticeBill(noticeBill);
workBill.setStaff(decidedZone.getStaff());
workBill.setType("新");
workBill.setPickstate("新单");
workBill.setBuildtime(new Timestamp(System.currentTimeMillis()));
workBill.setAttachbilltimes(0);
workBill.setRemark(noticeBill.getRemark());
workBillDAO.save(workBill);
}
}
3.2.4. 配置添加通知单Action
<!-- 业务通知单受理 -->
<action name="noticebill_*" class="noticeBillAction" method="{1}">
<result name="saveSUCCESS">
/WEB-INF/pages/qupai/noticebill_add.jsp
</result>
</action
>
4. 业务受理 未实现的功能
工单操作
查询出系统的所有工单 qp_workbill
追单 : 客户已经提交业务申请,取派员很多都没有去客户家中取件,再次进行追单操作, 系统只需要在 workbill表中 attachbilltimes 追单次数+1 (实际业务中,需要通过短信平台,催促取派员 )
销单 : 在取派员去取件的过程中,客户选择进行销单,不进行快递业务 ,在workbill 表type 设置 “销”, pickstack 设置为 “已取消”
查台转单
业务通知单 自动分单 成功 ,但是分错人了, 需要为通知单 重新手动更换 取派员 !
人工调度
业务通知单 自动分单失败,进入人工调度,需要手动为通知单指定取派员,生成工单!
结果: 已经将客户的货物,取回了物流公司!!!
5. 工作单管理
工作单基础信息的录入是信息的入口,并且也是本系统中人机交互最多的环节;基础信息录入的及时、准确、完整会直接影响后续的实物的运转配载、小件员交款、财务收入确认、成本垫付分摊,所以对数据的准确性、及时性、完整性要求比较高。
5.1. 工作单快速录入
提供操作人员在货量大单子多时简洁快速录入工作单的途径,快速录入中的信息主要是为了满足配载而设置的功能界面。
当业务高峰期,业务人员,只需要录入工作单基本信息(为了满足物流),就可以开始物流配送, 在业务闲暇时,再手动完善工作单信息。
5.1.1. easy ui 提供 datagrid的 行编辑效果
使用 datagrid 的列属性
步骤一: 在可编辑 列信息上,添加editor 属性
步骤二: 如果想编辑数据,开启某行数据编辑的状态
开启编辑
$('#grid').datagrid('beginEdit',1);// 索引1 代表第二行
结束编辑
$('#grid').datagrid('endEdit',1);// 索引1 代表第二行
取消编辑 (将该行数据,还原到编辑之前的效果 )
$('#grid').datagrid('cancelEdit',1);// 索引1 代表第二行
步骤三: 如何向表格插入新的一行
Easy ui 提供 appendRow 和 insertRow 用于表格数据插入 appendRow
在表格末尾追加一行 insertRow
在指定位置插入一行
在表格第一行,插入一个空白行 :
$('#grid').datagrid('insertRow',{
index : 0 , // 在第一行进行插入
row : {}
});
步骤四: 在编辑结束后 或者 取消编辑后,执行事件函数
在行编辑后,onAfterEditor 事件函数就会执行,在取消编辑后 onCancelEdit 会执行
onAfterEdit : function(rowIndex, rowData, changes){
alert('编辑完成');
}
5.1.2. 工作单快速录入页面
/WEB-INF/pages/qupai/quickworkorder.jsp
// 点击新增一行
function doAdd(){
// 判断当前是否正在编辑
if(editIndex != undefined){
$("#grid").datagrid('endEdit',editIndex); // 结束当前行编辑
// 触发onAfterEdit 函数
}
// 判断当前已经没有编辑行
if(editIndex==undefined){
// 在数据表格第一行 ,插入一个空行
$("#grid").datagrid('insertRow',{
index : 0,
row : {}
});
// 打开第一行编辑状态
$("#grid").datagrid('beginEdit',0);
// 将编辑的行号,保存成员变量
editIndex = 0;
}
}
datagrid 行编辑,如果该行数据项不符合form的校验,无法执行 endEdit方法
在datagrid的每行结束编辑时,触发doAfterEdit 事件,在事件中提交ajax请求,提交结束编辑行数据给服务器端,完成数据保存
onAfterEdit : function(rowIndex, rowData, changes){
editIndex = undefined; // 将当前正在编辑行 设置undefined
// 提交ajax请求,将编辑行数据,以ajax方式,发送到服务器,完成保
$.post("${pageContext.request.contextPath}/workordermanage_saveOrUpdate.action",rowData , function(data){
});
}
编写服务器代码
public class WorkOrderManageAction extends BaseAction implements ModelDriven<WorkOrderManage> {
}
public interface WorkOrderManageService {
}
public class WorkOrderManageServiceImpl extends BaseService implements WorkOrderManageService {
}
将DAO 注入 BaseService
将Service 注入BaseAction
Action 处理ajax返回结果
配置struts.xml 拆分
<include file="struts-user.xml"></include>
<include file="struts-bc.xml"></include>
<include file="struts-qp.xml"></include>
在 struts-qp.xml 配置工作单
<!-- 工作单管理 -->
<action name="workordermanage_*" class="workOrderManageAction" method="{1}">
<!-- 保存 -->
<result name="saveOrUpdateSUCCESS" type="json">
<param name="root">map</param>
</result>
</action>
5.2. 工作单快速录入 分页列表查询
为datagrid 添加url
url : "${pageContext.request.contextPath}/workordermanage_pageQuery.action",
在服务器 WorkOrderManageAction 添加 pageQuery 方法
<!-- 分页查询 -->
<result name="pageQuerySUCCESS" type="json">
<param name="root">pageResponseBean</param>
<param name="includeProperties">
total,
rows\[\d+\]\.id,
rows\[\d+\]\.arrivecity,
rows\[\d+\]\.product,
rows\[\d+\]\.num,
rows\[\d+\]\.weight,
rows\[\d+\]\.floadreqr
</param>
</result>
5.3. 工作单搜索功能
对工作单使用 like模糊查询时,实际上 数据库内部索引无法使用 ,需要逐条比较查询内容,效率比较低
在数据量很多情况下, 提供模糊查询性能,使用lucene全文索引库技术
—- 最适合 场景 : 论坛和贴吧
Lucene检索原理, 会针对目标内容,先进行分词建立全文索引 ,在用户查找时,先查询索引库中词条,根据词条找到数据记录id ,再根据id 查找数据库记录 !
5.3.1. 在添加工作单,为工作单数据建立索引
直接引入框架 Hibernate Search (用来整合 Hibernate + Lucene)
HibernateSearch是在apacheLucene的基础上建立的主要用于Hibernate的持久化模型的全文检索工具。
1、 在项目导入jar包
导入 hibernate 的jar 、 导入 lucene的jar 、导入hibernate search的jar
dist 存放jar包
docs 文档
project 项目源码
hibernate-search-3.4.2.Final.jar : hibernate Search 核心jar包
hibernate-core-3.6.10.Final.jar : hibernate 核心jar包
lucene-core-3.1.0.jar : lucene核心jar包
使用maven坐标 导入 hibernate search
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search</artifactId>
<version>3.4.2.Final</version>
</dependency>
2、 导入 IK 分词器
将 IKAnalyzer2012_u6.jar 导入项目
将 IKAnalyzer.cfg.xml、stopword.dic 两个文件 复制 classpath
将jar复制到 项目src/main/webapp/WEB-INF/lib 下
<!-- 导入 IK分词器 -->
<dependency>
<groupId>ik</groupId>
<artifactId>ik</artifactId>
<version>1.0</version> <systemPath>D:\work\sh_javaee20130731\mavenbos\src\main\webapp\WEB-INF\lib\IKAnalyzer2012_u6.jar</systemPath>
<scope>system</scope>
</dependency>
3、 配置hibernate search
在applicationContext-common.xml 中配置 索引库位置
<prop key="hibernate.search.default.indexBase">d:/index</prop>
在实体类上面使用注解配置对哪些数据建立索引(只支持注解)
自动完成索引库 创建、 更新、删除 !!!!
使用 Luke 工具,查询索引文件内容 !!
5.3.2. 结合lucene索引库完成模糊查找功能
使用 EasyUI 提供 searchbox 完成搜索框制作
<!-- prompt 默认提示内容 menu 搜索条件下拉选项 searcher 点击搜索按钮执行js函数名称 -->
<input id="ss" class="easyui-searchbox" style="width:300px" data-options="prompt:'请输入您的查询内容',menu:'#mm',searcher:doSearch"/>
<div id="mm">
<div data-options="name:'arrivecity'">按照到达地搜索</div>
<div data-options="name:'product'">按照货物名称搜索</div>
</div>
// 搜索函数
function doSearch(value,name){
alert("搜索项:"+name + ", 搜索内容:" + value);
}
将搜索内容,提交给服务器
// 将查询条件 缓存到 datagrid
$('#grid').datagrid('load',{
conditionName : name,
conditionValue : value
});
修改 WorkOrderManageAction的 pageQuery 方法,结合条件查询
在GenericDAO 添加查询lucene索引库的方法
/** * 结合lucene索引库 进行分页查询 * * @param conditionName * @param conditionValue * @param page * @param rows * @return */
public PageResponseBean queryByLucene(String conditionName, String conditionValue, int page, int rows);