fy36
2025-05-14 a37aca60ff9914b0abb710f04118b22420f4f398
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
--[[
   编码: 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