--[[
|
编码: JX-60-12
|
名称: 自动配盘
|
作者:
|
日期: 2025-1-29
|
|
函数: main
|
功能:
|
-- 根据出库波次明细计算出配盘及配盘明细,再把出库货品批分到各出库单绑定的拣料箱
|
|
备注:
|
-- 该程序起源巨星二期料箱库,适合多料格料箱的出库分拣操作
|
|
更改记录:
|
V2.0 合并取消 combine_cntr_detail
|
--]]
|
wms_out = require( "wms_outbound" )
|
wms_station = require( "wms_station" )
|
|
local function get_d_cntr_detail_by_bs_no( d_cntr_detail_list, bs_no )
|
local sub_d_cntr_detail_list = {}
|
local n
|
|
for n = 1, #d_cntr_detail_list do
|
if ( d_cntr_detail_list[n].bs_no == bs_no ) then
|
table.insert( sub_d_cntr_detail_list, d_cntr_detail_list[n])
|
end
|
end
|
return sub_d_cntr_detail_list
|
end
|
|
-- 根据体积进行排序,体积大的放前面
|
local function dc_detail_sort_by_volume( t1, t2 )
|
return t1.volume*t1.qty > t2.volume*t2.qty
|
end
|
|
local function picking_box_sort_by_volume( t1, t2 )
|
return t1.box_volume> t2.box_volume
|
end
|
|
-- d_cntr_detail_list ( 某个出库单相关的配盘明细 ) pick_box_code 拣料箱编码
|
-- new_d_cntr_detail_list 批分结果
|
local function split_by_picking_box( strLuaDEID, d_cntr_detail_list, picking_box_code, new_d_cntr_detail_list )
|
local nRet, strRetInfo
|
local seg = lua.split( picking_box_code, ";" )
|
local n, m
|
|
-- 初始化拣料箱
|
local picking_box_list = {}
|
local box_volume = wms_base.Get_nConst( strLuaDEID, "拣料箱体积")
|
local pick_box_num = #seg
|
for n = 1, pick_box_num do
|
local picking_box = {
|
picking_box_code = seg[n],
|
box_volume = box_volume,
|
}
|
table.insert( picking_box_list, picking_box )
|
end
|
|
-- 把体积大的放前面
|
table.sort( d_cntr_detail_list, dc_detail_sort_by_volume )
|
|
-- 批分到不同的拣料箱
|
local find, volume, qty, need_split_qty
|
|
for n = 1, #d_cntr_detail_list do
|
find = false
|
volume = d_cntr_detail_list[n].qty*d_cntr_detail_list[n].volume
|
for m = 1, pick_box_num do
|
if ( volume <= picking_box_list[m].box_volume ) then
|
d_cntr_detail_list[n].pick_box_code = picking_box_list[m].picking_box_code
|
picking_box_list[m].box_volume = picking_box_list[m].box_volume - volume
|
find = true
|
break
|
end
|
end
|
if ( find ) then
|
table.insert( new_d_cntr_detail_list, d_cntr_detail_list[n] )
|
else
|
-- 需要批分,把一条【配盘明细】根据拣料箱
|
need_split_qty = d_cntr_detail_list[n].qty -- 需要批分的数量
|
|
for m = 1, pick_box_num do
|
if ( picking_box_list[m].box_volume > 0 ) then
|
qty = math.floor( picking_box_list[m].box_volume/ d_cntr_detail_list[n].volume )
|
if ( need_split_qty < qty ) then
|
qty = need_split_qty
|
end
|
need_split_qty = need_split_qty - qty
|
|
local d_cntr_detail = {
|
item_code = d_cntr_detail_list[n].item_code,
|
item_name = d_cntr_detail_list[n].item_name,
|
cntr_code = d_cntr_detail_list[n].cntr_code,
|
batch_no = d_cntr_detail_list[n].batch_no,
|
cell_no = d_cntr_detail_list[n].cell_no,
|
station = d_cntr_detail_list[n].station,
|
|
serial_no = d_cntr_detail_list[n].serial_no,
|
item_spec = d_cntr_detail_list[n].item_spec,
|
end_user = d_cntr_detail_list[n].end_user,
|
owner = d_cntr_detail_list[n].owner,
|
uom = d_cntr_detail_list[n].uom,
|
volume = d_cntr_detail_list[n].volume,
|
weight = d_cntr_detail_list[n].weight,
|
wh_code = d_cntr_detail_list[n].wh_code,
|
area_code = d_cntr_detail_list[n].area_code,
|
loc_code = d_cntr_detail_list[n].loc_code,
|
|
cg_detail_id = d_cntr_detail_list[n].cg_detail_id,
|
bs_type = d_cntr_detail_list[n].bs_type,
|
bs_no = d_cntr_detail_list[n].bs_no,
|
bs_row_no = d_cntr_detail_list[n].bs_row_no,
|
wave_no = d_cntr_detail_list[n].wave_no,
|
wave_cls_id = d_cntr_detail_list[n].wave_cls_id,
|
|
pick_box_code = picking_box_list[m].picking_box_code,
|
qty = qty
|
}
|
picking_box_list[m].box_volume = picking_box_list[m].box_volume - qty*d_cntr_detail_list[n].volume
|
table.insert( new_d_cntr_detail_list, d_cntr_detail )
|
end
|
if ( need_split_qty == 0 ) then
|
break
|
end
|
end
|
|
-- 如果还有 need_split_qty 说明无法分配拣货箱,这是有问题的需要报警
|
if ( need_split_qty > 0 ) then
|
return 1, "货品'"..d_cntr_detail_list[n].item_code.."'因为体积的原因无法分配拣料箱!"
|
end
|
end
|
|
-- 把拣料箱可用容积最大的放前面
|
table.sort( picking_box_list, picking_box_sort_by_volume )
|
end
|
return 0
|
end
|
|
|
-- 把配盘明细中相同料格的货品数量进行合并
|
-- Distribution_CNTR_Detail 合并,在出库的时候一个料格可能会有多条CG_Detail
|
local function combine_cntr_detail( d_cntr_detail_list )
|
local n
|
local combine_result = {}
|
|
for n = 1, #d_cntr_detail_list do
|
find = false
|
for m = 1, #combine_result do
|
if ( d_cntr_detail_list[n].cntr_code == combine_result[m].cntr_code and d_cntr_detail_list[n].cell_no == combine_result[m].cell_no ) then
|
if ( d_cntr_detail_list[n].item_code ~= combine_result[m].item_code ) then
|
return 1, "合并配盘明细发生错误, 相同的料格存在不同编码的货品!"
|
end
|
find = true
|
combine_result[m].qty = combine_result[m].qty + d_cntr_detail_list[n].qty
|
-- MDF BY HAN 20250314
|
combine_result[m].cg_detail_id = "" -- 多条合并后CG_Detail要设置为空
|
end
|
end
|
|
if ( find == false ) then
|
table.insert( combine_result, d_cntr_detail_list[n] )
|
end
|
end
|
|
return 0, combine_result
|
end
|
|
local function cg_detail_add_alloc_qty( strLuaDEID, d_cntr_detail_list )
|
local m, nRet, strRetInfo
|
|
for m = 1, #d_cntr_detail_list do
|
-- CG_Detail 加分配量
|
strCondition = "S_ID = '"..d_cntr_detail_list[m].cg_detail_id.."'"
|
nRet, strRetInfo = mobox.incDataObjAccQty ( strLuaDEID, "CG_Detail", strCondition, "F_QTY", "F_ALLOC_QTY", lua.Get_NumAttrValue( d_cntr_detail_list[m].qty ) )
|
if ( nRet ~= 0 or strRetInfo ~= '') then
|
return 1, '在增加【容器货品明细】中的分配量时失败!'..strRetInfo
|
end
|
end
|
return 0
|
end
|
|
function main( strLuaDEID )
|
local nRet, strRetInfo
|
|
-- 获取出库波次对象属性
|
nRet, strRetInfo = mobox.getCurEditDataObjAttr( strLuaDEID, "S_WAVE_NO", "N_B_STATE","S_WH_CODE","S_AREA_CODE","S_STATION_NO" )
|
local input_value = json.decode( strRetInfo )
|
local wave_no = lua.Get_StrAttrValue( input_value[1].value )
|
local b_state = lua.Get_NumAttrValue( input_value[2].value )
|
local wh_code = lua.Get_StrAttrValue( input_value[3].value )
|
local area_code = lua.Get_StrAttrValue( input_value[4].value )
|
local station = lua.Get_StrAttrValue( input_value[5].value )
|
local err_msg = ''
|
local n, strCondition, m
|
local item_list = {}
|
local d_cntr_list = {} -- 配盘/Distribution_CNTR
|
local d_cntr_detail_list = {} -- 配盘明细/Distribution_CNTR_Detail
|
local oo_no_set = {}
|
local loc_code
|
nRet, loc_code = wms_station.Get_Station_Loc( strLuaDEID, station )
|
if ( nRet ~= 0 ) then
|
lua.Stop( strLuaDEID, loc_code )
|
return
|
end
|
|
local detail_attrs, ow_compose
|
local outbound_detail_list
|
local data_objs
|
local pickbox_volume = wms_base.Get_nConst( strLuaDEID, "拣料箱体积")
|
local oo_obj_attrs
|
local pick_box_num
|
local pick_box_code
|
local new_d_cntr_detail_list = {} -- 加了拣料箱编码后的 【配盘明细】
|
local paramter = {}
|
|
local exit_loc
|
nRet, exit_loc = wms_wh.GetLocInfo( loc_code )
|
if ( nRet ~= 0 ) then
|
err_msg = '获取站台货位信息失败! '..loc_code
|
goto set_state_ret
|
end
|
|
if ( wave_no == '' ) then
|
err_msg = '出库波次编码不能为空!'
|
goto set_state_ret
|
end
|
if ( wh_code == '' ) then
|
err_msg = '出库波次中仓库编码不能为空!'
|
goto set_state_ret
|
end
|
|
-- 获取 出库波次 的出库单组成,生成一个 oo_no_set 对象,["OW01","OW02",...]
|
strCondition = "S_WAVE_NO = '"..wave_no.."'"
|
nRet, data_objs = m3.QueryDataObject(strLuaDEID, "OW_Compose", strCondition, "S_OO_NO" )
|
if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objs end
|
if ( data_objs == '' ) then
|
-- 设置错误信息
|
err_msg = "波次号'"..wave_no.."'的出库波次没有组成对象!"
|
goto set_state_ret
|
end
|
for n = 1, #data_objs do
|
ow_compose = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
|
table.insert( oo_no_set, ow_compose.S_OO_NO )
|
end
|
|
-- 获取 出库波次 货品明细, 生成 item_list 需要出库的货品清单
|
strCondition = "S_WAVE_NO = '"..wave_no.."'"
|
nRet, data_objs = m3.QueryDataObject(strLuaDEID, "OW_Detail", strCondition, "N_ROW_NO" )
|
if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objs end
|
if ( data_objs == '' ) then
|
-- 设置错误信息
|
err_msg = "波次号'"..wave_no.."'的出库波次明细为空!"
|
goto set_state_ret
|
end
|
for n = 1, #data_objs do
|
detail_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
|
local item = {
|
item_code = lua.Get_StrAttrValue( detail_attrs.S_ITEM_CODE ),
|
item_name = lua.Get_StrAttrValue( detail_attrs.S_ITEM_NAME ),
|
volume = lua.Get_NumAttrValue( detail_attrs.F_VOLUME),
|
weight = lua.Get_NumAttrValue( detail_attrs.F_WEIGHT),
|
cell_type = lua.Get_StrAttrValue( detail_attrs.S_CELL_TYPE ),
|
qty = lua.Get_NumAttrValue( detail_attrs.F_QTY),
|
alloc_qty = 0,
|
cntr_cell_list = {},
|
ok = false -- true 表示这个货品已经配完货
|
}
|
table.insert( item_list, item )
|
end
|
|
-- 【step1】 系统自动配货算法
|
-- wms_out.Distribution_Procedure 是汉和WMS的一个标准的配盘算法
|
-- MDF BY WHB 20250110 area_code 改成空(原因是可以在输送线上获取容器,否则就只能是存储区匹配)
|
paramter = {
|
wh_code = wh_code, area_code = '',
|
exit_loc = exit_loc,
|
bs_type = "Outbound_Wave",
|
bs_no = wave_no,
|
order_by = "S_BATCH_NO",
|
station = station
|
}
|
nRet, strRetInfo = wms_out.Distribution_Procedure( strLuaDEID, item_list, paramter, d_cntr_list, d_cntr_detail_list, 1 )
|
if ( nRet ~= 0 ) then
|
err_msg = "【step1】系统自动配货算法,详细信息见日志!"..strRetInfo
|
lua.Debug( strLuaDEID, debug.getinfo(1), "【step1】系统自动配货算法出错", "wms_out.Distribution_Procedure 返回错误: "..strRetInfo )
|
goto set_state_ret
|
end
|
|
lua.Debug( strLuaDEID, debug.getinfo(1), "配盘 --> ", d_cntr_list )
|
lua.Debug( strLuaDEID, debug.getinfo(1), "配盘明细1 --> ", d_cntr_detail_list )
|
|
-- CG_Detail 加分配量
|
nRet, strRetInfo = cg_detail_add_alloc_qty( strLuaDEID, d_cntr_detail_list )
|
if ( nRet ~= 0 ) then
|
err_msg = "【step1.1】CG_Detail 加分配量失败,详细信息见日志!"..strRetInfo
|
lua.Debug( strLuaDEID, debug.getinfo(1), "【step1.1】CG_Detail 加分配量失败", strRetInfo )
|
goto set_state_ret
|
end
|
|
-- V2.0
|
-- 配盘明细根据料格合并相同货品数量
|
nRet, d_cntr_detail_list = combine_cntr_detail( d_cntr_detail_list )
|
if ( nRet ~= 0 ) then
|
err_msg = "【step1.2】合并配盘明细错误!"..d_cntr_detail_list
|
lua.Debug( strLuaDEID, debug.getinfo(1), "【step1.2】合并配盘明细错误", d_cntr_detail_list )
|
goto set_state_ret
|
end
|
lua.Debug( strLuaDEID, debug.getinfo(1), "配盘明细 after combine --> ", d_cntr_detail_list )
|
|
--【step2】 如果采用波次需要把【配盘明细】根据出库单明细进行批分
|
-- 因为是采用波次出库,在生成配盘明细的 bs_type 的时候用的是 波次及波次号,在后面需要进一批批分到出库单上
|
-- 如果没采用波次可以直接用 d_cntr_detail_list 生成数据
|
|
-- step2.1 获取 出库单明细全部,如果有多个出库单把这些出库单的明细全部加在 outbound_detail_list
|
nRet, outbound_detail_list = wms_out.Get_Outbound_Detail_list( strLuaDEID, oo_no_set )
|
if ( nRet ~= 0 ) then
|
err_msg = "【step2.1】获取所有出库单明细时出错,详细信息见日志!"
|
lua.Debug( strLuaDEID, debug.getinfo(1), "【step2.1】获取所有出库单明细时出错", "wms_out.Get_Outbound_Detail_list 返回错误: "..outbound_detail_list )
|
goto set_state_ret
|
end
|
-- step 2.2 批分出库单明细
|
d_cntr_detail_list = wms_out.Split_Distribution_CNTR_Detail( strLuaDEID, wave_no, "Outbound_Wave", d_cntr_detail_list, outbound_detail_list )
|
lua.Debug( strLuaDEID, debug.getinfo(1), "配盘明细2 --> ", d_cntr_detail_list )
|
|
-- step 2.3 检查出库单中的拣料箱数量,如果大于1的要根据拣料箱进行批分(明确这些拣料箱放哪些检出的货品)
|
-- HAN 20241031 新增变更
|
-- 找出波次中拣料箱数量大于1的出库单,这些出库单需要对拣货箱进行批分
|
strCondition = "S_WAVE_NO = '"..wave_no.."'"
|
nRet, data_objs = m3.QueryDataObject(strLuaDEID, "Outbound_Order", strCondition, "S_NO" )
|
if (nRet ~= 0) then
|
err_msg = "QueryDataObject失败!"
|
goto set_state_ret
|
end
|
|
for n = 1, #data_objs do
|
oo_obj_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
|
pick_box_num = lua.Get_NumAttrValue( oo_obj_attrs.N_PICKING_BOX_NUM )
|
pick_box_code = lua.Get_StrAttrValue( oo_obj_attrs.S_PICK_BOX_CODE )
|
if (pick_box_code == '' or pick_box_num == 0 ) then
|
err_msg = "出库单'"..oo_obj_attrs.S_NO.."'没有绑定拣料箱!"
|
goto set_state_ret
|
end
|
-- 从 d_cntr_detail_list 获取该出库单号的【配盘明细】
|
sub_d_cntr_detail_list = get_d_cntr_detail_by_bs_no( d_cntr_detail_list, oo_obj_attrs.S_NO )
|
if ( pick_box_num == 1 ) then
|
-- 把拣料箱编码加入 d_cntr_detail
|
for m = 1, #sub_d_cntr_detail_list do
|
sub_d_cntr_detail_list[m].pick_box_code = pick_box_code
|
table.insert( new_d_cntr_detail_list, sub_d_cntr_detail_list[m] )
|
end
|
else
|
nRet, strRetInfo = split_by_picking_box( strLuaDEID, sub_d_cntr_detail_list, pick_box_code, new_d_cntr_detail_list )
|
|
lua.Debug( strLuaDEID, debug.getinfo(1), "拣货箱批分后-->", new_d_cntr_detail_list )
|
|
if ( nRet ~= 0 ) then
|
err_msg = "出库单'"..oo_obj_attrs.S_NO.."'在批分拣料箱时发生错误, 具体信息请见日志!"
|
lua.Debug( strLuaDEID, debug.getinfo(1), "【step2.3】 批分拣料箱时出错", "split_by_picking_box 返回错误: "..strRetInfo )
|
goto set_state_ret
|
end
|
end
|
end
|
|
--【step3】 创建配盘及配盘明细
|
nRet, strRetInfo = wms_out.Creat_Distribution_list( strLuaDEID, d_cntr_list, new_d_cntr_detail_list )
|
if ( nRet ~= 0 ) then
|
err_msg = "wms_out.Creat_Distribution_list 发生错误,具体信息请查看系统日志!"
|
lua.Debug( strLuaDEID, debug.getinfo(1), "【step3】 创建配盘及配盘明细出错", "wms_out.Creat_Distribution_list 返回错误: "..strRetInfo )
|
goto set_state_ret
|
end
|
|
-- 设置出库波次的状态
|
::set_state_ret::
|
strCondition = "S_WAVE_NO = '"..wave_no.."'"
|
local strSetAttr
|
|
if ( err_msg ~= '' ) then
|
-- 5 错误
|
strSetAttr = "N_B_STATE = 5, S_ERR_MSG = '"..lua.FormatSQLString(err_msg).."', N_PRE_B_STATE = "..b_state
|
else
|
-- 2 配货完成
|
strSetAttr = "N_B_STATE = 2, S_ERR_MSG = ''"
|
end
|
nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Outbound_Wave", strCondition, strSetAttr )
|
if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "更新【出库波次】信息失败!"..strRetInfo ) end
|
|
if ( err_msg ~= '' ) then
|
mobox.addProcSQL3("Outbound_Wave", strCondition, strSetAttr)
|
mobox.stopProgram( strLuaDEID, "自动配盘失败!"..err_msg )
|
return
|
end
|
-- 设置出库单状态 = 2 (配货完成)
|
if ( err_msg == '' ) then
|
strSetAttr = "N_B_STATE = 2"
|
nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Outbound_Order", strCondition, strSetAttr )
|
if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "更新【出库单】信息失败!"..strRetInfo ) end
|
|
-- 触发后台脚本生成出库作业
|
local strCurEditClsID, strCurEditObjID
|
nRet, strCurEditClsID, strCurEditObjID = mobox.getCurEditDataObjID( strLuaDEID )
|
if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "getCurEditDataObjID失败! " ) end
|
local add_wfp = {
|
wfp_type = 1,
|
cls = "Outbound_Wave",
|
obj_id = strCurEditObjID,
|
obj_name = "出库波次'"..wave_no.."'-->生成出库作业",
|
trigger_event = "后台创建出库作业"
|
}
|
nRet, strRetInfo = m3.AddSysWFP( strLuaDEID, add_wfp )
|
if ( nRet ~= 0 ) then
|
lua.Error( strLuaDEID, debug.getinfo(1), "AddSysWFP失败!"..strRetInfo )
|
end
|
end
|
end
|