1、准备数据
完成下面几个类的基本CRUD,具体的页面后面完成
- systemdictionarytype:数据字典类型
- systemdictionarydetail:数据字典明细
- Supplier:供应商
- Product:产品
2、采购订单分析
拿到数据库中的表,主要从下面3点进行分析
- 每个字段的含义
- 这个字段是否可以为null
- 这个字段从哪里来?
2.1、采购订单Purchasebill
- 订单与订单详细为组合关系,组合关系为强聚合
- 聚合关系为多对一和一对多的关系,强聚合为不可分割的聚合关系
- 组合关系,一般一方放弃维护关系,交给多方(mappedBy)
- CascadeType级联,使用all。并且使用orphanRemoval
- 级联保存,确保一方找到多方,多方找到一方
- 级联删除,比较危险,删除自己并且删除关系的,所以使用orphanRemoval,先解除关系,再删除
/*
* 采购订单
* */
@Entity
@Table(name = "purchasebill")
public class Purchasebill extends BaseDomain {
//交易时间,前台传来
private Date vdate;
//总金额,计算
private BigDecimal totalAmount;
//总数量,计算出来
private BigDecimal totalNum;
//录入时间,当前系统时间
private Date inputTime = new Date();
//审核时间,可以为空,当前系统时间
private Date auditorTime;
//状态,0待审。1已申。2作废
private Integer status = 0;
//供应商,optional不能为空,前台传来
@ManyToOne(fetch = FetchType.LAZY,optional = false)
@JoinColumn(name = "supplier_id")
private Supplier supplier;
//审核员,可以为空,自动获取当前登录的
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "auditor_id")
private Employee auditor;
//采购员,不能为空,前台传过来
@ManyToOne(fetch = FetchType.LAZY,optional = false)
@JoinColumn(name = "buyer_id")
private Employee buyer;
//录入人,当前登录人
@ManyToOne(fetch = FetchType.LAZY,optional = false)
@JoinColumn(name = "inputUser_id")
private Employee inputUser;
// 一般组合关系使用List,mappedBy放弃维护,交给多方,注意bill与多方的字段名必须一致。
//CascadeType级联,级联删除,删除自己并且删除关系的
//orphanRemoval孤儿删除,先解除关系,再删除
//级联保存,双向都必须找到对方
@OneToMany(cascade = CascadeType.ALL, mappedBy = "bill", fetch = FetchType.LAZY, orphanRemoval = true)
private List<Purchasebillitem> items = new ArrayList<Purchasebillitem>();
//get set
2.2、采购订单明细Purchasebillitem
/*
* 采购订单明细
* */
@Entity
@Table(name = "purchasebillitem")
public class Purchasebillitem extends BaseDomain {
//单价,成本价,前台传
private BigDecimal price;
//数量,前台传
private BigDecimal num;
//总数量,算出来
private BigDecimal amount;
//描述,可以为空,传过来
private String descs;
//采购单
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "bill_id")
@JsonIgnore
private Purchasebill bill;
//商品
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "product_id")
private Product product;
//get set
3、数据展示
3.1、purchasebill/index.jsp
供应商、采购员、状态格式化展示
<tr>
<th data-options="field:'',width:100,checkbox:true">多选</th>
<th data-options="field:'id',width:100">序号</th>
<th data-options="field:'vdate',width:100">交易时间</th>
<th data-options="field:'supplier',width:100,formatter:objFormat">供应商</th>
<th data-options="field:'buyer',width:100,formatter:objFormat">采购员</th>
<th data-options="field:'totalAmount',width:100">总金额</th>
<th data-options="field:'totalNum',width:100">总数量</th>
<th data-options="field:'status',width:100,formatter:statusFormat">状态</th>
</tr>
3.2、purchasebill.js
function objFormat(v) {
return v?v.name||v.username:"";
}
function statusFormat(v) {
if(v == 0){
return "<span style='color: red'>待审</span>";
}else if (v == 1){
return "<span style='color: green;'>已审</span>";
}else{
return "<s style='color: grey;'>作废</s>";
}
}
4、高级查询
根据时间和状态查询
4.1、purchasebill/index.jsp
<form id="searchForm" method="post">
交易时间: <input name="beginDate" class="easyui-datebox" style="width:120px"> -
<input name="endDate" class="easyui-datebox" style="width:120px">
状态: <select class="easyui-combobox" name="status" style="width:100px;" panelHeight="auto">
<option value="">--所有--</option>
<option value="0">待审</option>
<option value="1">已审</option>
<option value="-1">作废</option>
</select>
<a href="#" data-method="search" class="easyui-linkbutton" iconCls="icon-search">查询</a>
</form>
4.2、PurchasebillQuery
public class PurchasebillQuery extends BaseQuery {
//开始时间
private Date beginDate;
//结束时间
private Date endDate;
//状态
private Integer status;
private Long isdelete;
/*
*这里需要注意:
* 如果日期中出现了时间,如2015-04-21 00:00:00
* 在使用2015-04-21 - 2015-04-21 查询时,会出现只能查到2015-04-21 00:00:00,而2015-04-21 12:00:00查不到
* 这里可以使用日期+1的方法解决这个问题,但是又会出现一个新的问题,会查到2015-04-22 00:00:00
* 这样的话就把le(小于等于改成lt小于)
*
* */
@Override
public Specification createSpecification() {
//结束时间加1天,lang3包内的方法
if(endDate!=null){
endDate = DateUtils.addDays(endDate,1);
}
Specification<PurchasebillQuery> specification = Specifications.<PurchasebillQuery>and()
//大于等于
.ge(beginDate != null, "vdate", beginDate)
//小于等于
.lt(endDate != null, "vdate", endDate)
.eq(status!=null,"status",status)
.eq(true, "isdelete", 1L)
.build();
return specification;
}
@Override
public Specification createSpecificationRe() {
Specification<PurchasebillQuery> specification = Specifications.<PurchasebillQuery>and()
//大于等于
.ge(beginDate != null, "vdate", beginDate)
//小于等于
.lt(endDate != null, "vdate", endDate)
.eq(status!=null,"status",status)
.eq(true, "isdelete", 2L)
.build();
return specification;
}
}
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setBeginDate(Date beginDate) {
this.beginDate = beginDate;
}
@DateTimeFormat(pattern = "yyyy-MM-dd")
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
//get set
5、添加
需要从前台传过去的内容有:
- 交易时间vdate
- 供应商supplier.id
- 采购员buyer.id
- 订单明细items
- 订单明细使用EasyUI扩展中的一个插件datagrid-edit
- 级联保存,一方找到多方,多方也要找到一方,在controller中设置
5.1、purchasebill/index.jsp
引入必须js库,其中[query-1.11.0.js]、[jquery.jdirk.js]、[jquery.easyui.min.js]已经在head.js中引用
<%--引入编辑支持的js--%>
<script src="/easyui/plugin/cellEdit/jeasyui.extensions.datagrid.getColumnInfo.js"></script>
<script src="/easyui/plugin/cellEdit/jeasyui.extensions.datagrid.editors.js"></script>
<script src="/easyui/plugin/cellEdit/jeasyui.extensions.datagrid.edit.cellEdit.js"></script>
准备一个table及两个按钮,在js中动态创建datagrid-edit
<table id="itemsGrid" style="width: 800px"></table>
<div id="itemsButtons">
<a href="#" id="btnInsert" plain="true" iconCls="icon-add" class="easyui-linkbutton">添加</a>
<a href="#" id="btnRemove"plain="true" iconCls="icon-remove" class="easyui-linkbutton">删除</a>
</div>
5.2、purchasebill.jsp
添加,自动生成交易时间
add() {
//打开面板前把里面的数据清除
editForm.form("clear");
//修改添加框的标题
editDialog.dialog({title:'添加用户'});
//设置交易时间
$("#nowdate").datebox("setValue", '12/32/2020');
//把添加框(editDialog)打开
editDialog.dialog("center").dialog("open");
//数据进行清空(用空数据把原来数据给换了)
dg.datagrid("loadData",[]);
},
创建itemsGrid
/**
* dg:获取到你要编辑的grid控件
* defaultRow:默认行(等会儿会试一下)
* insertPosition:插入的位置 bottom/top
*/
var dg = $("#itemsGrid"),
defaultRow = { product: "", productColor: "", productPic: "", num: 1, price: 0, amount: 0, descs: "" },
insertPosition = "bottom";
/**
* grid控件的初始化工作
*/
var dgInit = function () {
//获取列数据
var getColumns = function () {
var result = [];
//列标题
/**
* editor:代表这一列是可以编辑的
* type:代表这个编辑器类型
* options:代表这个编辑器的属性
*/
var normal = [
{
field: 'product', title: '产品', width: 100,
editor: {
type: "combobox",
options: {
required: true,
valueField:'id',textField:'name',panelHeight:'auto',url:'/product/list'
}
},
//注意:这个应该和列同级
formatter:objFormat
},
{
field: 'productColor', title: '颜色', width: 100,
// v:格子中数据 r:行数据 i:索引
formatter:function (v,r,i) {
if(r && r.product){
return `<div style="width: 20px;height: 20px;background-color:${r.product.color}"></div>`;
}
return "";
}
},
{
field: 'productPic', title: '图片', width: 100,
// v:格子中数据 r:行数据 i:索引
formatter:function (v,r,i) {
if(r && r.product){
return `<image src="${r.product.smallPic}" style="width: 50px;height: 50px;" />`;
}
return "";
}
},
{
field: 'num', title: '数量', width: 100,
editor: {
type: "numberbox",
options: {
precision:2,
required: true
}
}
},
{
field: 'price', title: '价格', width: 100,
editor: {
type: "numberbox",
options: {
precision:2,
required: true
}
}
},
{
field: 'amount', title: '小计', width: 100,
formatter:function (v,r,i) {
if(r && r.num && r.price){
// toFixed:保留两位小数
return (r.num * r.price).toFixed(2);
}
return "";
}
},
{
field: 'descs', title: '备注', width: 100,
editor: {
type: "text"
}
}
];
result.push(normal);
return result;
};
//grid控件的属性
var options = {
idField: "ID",
rownumbers: true,
fitColumns: true,
fit: true,
border: true,
singleSelect: true,
columns: getColumns(),
bodyCls:"itemsBody",
toolbar:"#itemsButtons",
//表示开启单元格编辑功能
enableCellEdit: true
};
//创建grid控件
dg.datagrid(options);
};
/**
* 获取到插件的行位置
*/
var getInsertRowIndex = function () {
return insertPosition == "top" ? 0 : dg.datagrid("getRows").length;
}
/**
* 绑定事件
*/
var buttonBindEvent = function () {
//点击按键后添加一行数据
$("#btnInsert").click(function () {
var targetIndex = getInsertRowIndex(), targetRow = $.extend({}, defaultRow, { ID: $.util.guid() });
dg.datagrid("insertRow", { index: targetIndex, row: targetRow });
dg.datagrid("editCell", { index: 0, field: "Code" });
});
//删除一行数据
$("#btnRemove").click(function () {
//1.拿到这一行
var row = dg.datagrid("getSelected");
//2.拿到这一行的索引
var index = dg.datagrid("getRowIndex",row);
//3.根据索引删除它
dg.datagrid("deleteRow",index)
});
};
//页面加载完成后调用对应方法
dgInit(); buttonBindEvent();
保存
save(){
var url = "/purchasebill/save";
console.debug(editForm);
if ($("#purchasebillId").val()){
url = "/purchasebill/update?_cmd=update";
}
editForm.form('submit', {
//提交路径,变量名相同可以直接写
url:url,
onSubmit: function(param){
//拿到所有的行(明细的)
var rows = dg.datagrid("getRows");
for(var i=0;i<rows.length;i++){
var r = rows[i];
param[`items[${i}].product.id`] = r.product.id;
param[`items[${i}].num`] = r.num;
param[`items[${i}].price`] = r.price;
param[`items[${i}].descs`] = r.descs;
}
//提交前验证
return $(this).form('validate');
},
//data是一个json字符串{"success":true}
success:function(data){
//把一个Json字符串转成JSON对象
//eval("("+data+")")
var result = JSON.parse(data);
if(result.success){
//成功就刷新当前页面
datagrid.datagrid("reload");
}else{
$.messager.alert('警告',`添加失败,原因是:${result.msg}`,"warning");
}
//关闭对话框
xuxusheng.close();
}
});
},
5.3、controller
//将保存和修改两个方法合并
public JsonResult saveOrUpdate(Purchasebill purchasebill) {
try {
//设置录入员
Employee employee = UserContext.getUser();
purchasebill.setInputUser(employee);
//级联保存,需要一方找到多方,多方也能找到一方
//一方获取多方
List<Purchasebillitem> items = purchasebill.getItems();
//①.准备总金额与总数量
BigDecimal totalAmount = new BigDecimal(0);
BigDecimal totalNum = new BigDecimal(0);
for (Purchasebillitem item : items){
item.setBill(purchasebill);
//计算当前明细的小计 multiply:乘法
item.setAmount(item.getNum().multiply(item.getPrice()));
//②.计算总金额与总数量
totalAmount = totalAmount.add(item.getAmount());
totalNum = totalNum.add(item.getNum());
}
//③.把总金额与总数量放到采购订单中
purchasebill.setTotalAmount(totalAmount);
purchasebill.setTotalNum(totalNum);
purchasebill.setIsdelete(1L);
purchasebillService.save(purchasebill);
return new JsonResult();
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false, e.getMessage());
}
}
6、修改
数据回显,表单元素和非表单元素回显
edit() {
//获取选择的行
var row = datagrid.datagrid("getSelected");
//判断是否选择了一行
//没有选择提示并结束
if (!row) {
$.messager.alert('提示', '请先选择一行进行修改', "info");
return;
}else {
//修改添加框的标题
editDialog.dialog({title:'修改用户'});
//把添加框(editDialog)打开
editDialog.dialog("center").dialog("open");
//清空表单中的数据
editForm.form("clear");
//设置供应商的回显
if(row.supplier){
row["supplier.id"] = row.supplier.id;
}
if(row.buyer){
row["buyer.id"] = row.buyer.id;
}
//回显其他内容
editForm.form('load',row);
//回显grid中的数据
//1.拿到当前这一行的所有明细(复制)
var items = [...row.items];
//2.把明细放到grid中
dg.datagrid("loadData",items);
}
},