资源在此处下载。
GORM-groovy 对象关系映射中,最常见的不是1:1关系,而是1:M关系,也不是M:M关系(因为M:M关系比较麻烦,一般不能直接定义,通常都要拆分成两个1:M关系来处理,并且要引入第3个类,否则会出现问题)。在本项目中,最利于理解1:M的地方,就是“生产计划”planning域。
这不是一个单独的域,他分别与其他4个域类都发生了1:1或1:M关系。这些域都不能单独看待,而要整体看待。
1、 下面来讨论这5个域类:
1) 生产计划域
实际上就是根据订单而制订出的产品的具体生产过程。它包含了以下几个重要的内容:
首先,既然是根据订单进行生产,那么生产计划中必须包含有订单对象(即产品编号-前面已定义了这个域)。
其次,产品生产是要消耗一定原材料的,而且原材料可能不只一种,因此在生产计划中应当有物料表,以列出生产中可能用到的各种原材料。
最后,一个产品可能需要使用到一个或一系列的生产工艺才能制造出来,因此还应当在生产计划中列出工艺表,以列出可能用到的生产工艺。
生产计划中产品编号是一个相对简单属性,一个计划只针对一个产品,因此只会有一个产品编号,这只是1:1关系而已,前面已经定义了产品编号域,这里不再讨论。
属性物料表和工艺表则不同,它们包含的都是集合,由多个对象构成列表,因此这是1:m(1对多)关系,而集合里面的每个对象都是复杂对象,应当定义为域类。下面来一一讨论。当然,除了这些复杂对象外,生产计划还有一些简单的属性,比如简单的string 、int。该域类代码如下:
class Planning {
static constraints = {
productNo()
productName()
spec(nullable:true)
pieces()
inspection(nullable:true)
warehousing(nullable:true)
shipment(nullable:true)
factMaterial(nullable:true)
consumeMaterial(nullable:true)
accountsActual()
accountsDue()
rejection(nullable:true)
linkman(nullable:true)
remark(nullable:true)
}
static optionals=['spec','inspection','warehousing','shipment','factMaterial','consumeMaterial','accountsActual','accountsDue','rejection','linkman','remark']
ProductionNo productNo //产品编号
String productName //产品名
String spec //规格型号
static hasMany=[materials:Material,craftList:Crafts]//两个1对多关系:与物料表表项,与工艺表表项
int pieces //件数
String inspection //检验
String warehousing //入库
String shipment //出库,领用
String factMaterial //实际用料
String consumeMaterial //材料消耗
float accountsActual //实收金额
float accountsDue //应收金额
String rejection //退货
String linkman //联系人
String remark //备注
}
注意其中的2个地方。一个是
ProductionNo productNo //产品编号
这表明了一个1:1关系,因此我们还需要修改ProductionNo域类,加上这句:
static belongTo=[Planning]//定义1对1关系,即1个生产编号对应1个生产计划
另一个地方是
static hasMany=[materials:Material,craftList:Crafts]//两个1对多关系:与物料表表项,与工艺表表项
这定义了两个1:m关系。
2) 物料表
物料表实际上是1个以上的物料表表项组成的列表。物料表中的表项我们可以单独定义为一个域:
class Material {//Material域:定义生产计划中某个产品的一种原材料-物料表表项
static constraints = {
}
MaterialCost fee //定义一个材料的价格1:1关系
float amount=0 //该材料所需数量
}
而一个MaterialCost定义了一个材料的价格:
class MaterialCost {
static constraints = {
code(blank:false,nullable:false)//必填
name(blank:false,nullable:false)//必填
price(min:0.0f,nullable:false)//必填,值不能为负
unit()
remark()
}
static optionals=["unit","remark"]
String code //代码
String name //物料类型
float price //单价(元)
String unit='/u516C/u65A4' //单位,默认"公斤",经native2acii转码
String remark //备注
String toString(){
String.format('%1$s%2$s',code,name)
//"${coo.shortName}${prefix}-${sno}${suffix}"
}
}
3) 工艺表
工艺表表项也可以定义为一个单独的域:
class Crafts {//Crafts域:定义生产计划中某个产品的一种工艺 - 工艺表表项
static constraints = {
}
ProcessingFee fee //定义一个加工费,1:1关系
float hours //定义工时
int sequence //定义序号
}
其中引用了ProcessiongFee加工费。加工费定义了一种加工工艺的费用,域定义如下:
class ProcessingFee {//域:加工费
static constraints = {
code()
workType(nullale:true)
processFee(nullale:true)
labourFee(nullale:true)
remark(nullale:true)
}
String code //代码
String workType //工种
float processFee //加工费
float labourFee //人工费
String remark //备注
String toString(){
"${code} - ${workType}"
}
static optionals=["workType","processFee","labourFee","remark"]
}
2、 生成脚手架
定义完上述5个域类,再生成它们的脚手架代码。
3、 修改“生产计划”控制器中的action代码
由于我们希望在每编制一个新的“生产计划”都需要先编一个“产品编号”而不是直接就从编制“生产计划”开始,所以我们需要在PlanningController控制器修改action:
def create = {
redirect(controller: 'productionNo', action: 'create')
}
这样当url指向planningController的create动作时,实际上是调用productionNoController的create动作。
4、 修改“产品编号”控制器中的action
在产品编号create页面中,create按钮将调用productionNoController的save动作。我们也需要修改它,使得“产品编号”一编好,就显示“编制生产计划”页面:
def save = {
def productionNoInstance = new ProductionNo(params)
if(!productionNoInstance.hasErrors()) {
if(productionNoInstance.save()){
flash.message = "ProductionNo ${productionNoInstance.id} created"
redirect(action:'create_2',controller:'planning',params:['productNo.id':productionNoInstance.id])
}
}
else {
render(view:'create',model:[productionNoInstance:productionNoInstance])
}
}
注意划线部分的代码,redirect方法的前两个参数指定将跳转到PlanningController的create_2动作。别着急,这个create_2我们将在后面定义它。
值得注意的是第三个参数。我们本来是想把新建的“产品编号”对象实例传递给create_2的,然而最终我们只需要把对象实例的id传给create_2就可以了。Create_2会把这个id值代表的对象传递到页面去。
5、 编辑“生产计划”控制器中的action
在其中新增一个action:
def create_2 = {
def planningInstance = new Planning()
planningInstance.properties = params
render(view:'create',model:[planningInstance:planningInstance])
//return ['planningInstance':planningInstance]
}
这个action 渲染页面create.gsp。
6、 修改create.gsp
“生产计划”的属性有很多,我们在新建一个“生产计划”时,并不需要一次性把所有的属性都进行赋值——有的属性需要在新建时指定,而有的属性,很可能需要在生产完成之后再来填写它,因此需要修改create.gsp页面,使得在新建“生产计划”时不需要填写全部那么多的属性就可以新建一个生产计划。同时,需要注意的是产品编号这个字段,需要把它从下拉列表改为hidden控件。因为我们的业务要求用户在编制了产品编号后,才进行生产计划的编制——这时其实生产编号已经指定,不再需要用户从下拉列表中选取了:
……
<tr class="prop">
<td valign="top" class="name">
<label for="productNo">产品编号:</label>
</td>
<td valign="top" class="value ${hasErrors(bean:planningInstance,field:'productNo','errors')}">
${planningInstance?.productNo}
<input type="hidden" id="productNo" name="productNo.id" value="${planningInstance?.productNo?.id}" readonly/>
……
所以这里直接就把新建的产品编号显示出来,然后用一个隐藏域传递产品编号的id值。
7、 修改show.gsp
由于生产计划的字段有点多,我们需要对show.gsp进行重新排版,使显示更加美观:
8、