1
Jianw
9 天以前 70f29da38121b9a467841253e3268feb5df02902
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
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
--[[
     巨星二期WMS项目中Lua用到的一下公共函数
 
     更改记录:
     V3.0 HAN 20241127 增加对设备状态的获取
--]]
 
 
 
wms_wh   = require ("wms_wh")
wms_out  = require ("wms_outbound")
wms_alg  = require ("wms_base_algorithm")
jx_api   = require ("jx_external_api")
 
local jx_base = {_version = "0.1.1"}
 
--[[
    1# GetBoxStyle           根据物料的尺寸计算该物料适配的料箱规格
    2# Generate_Batch_No     生成货品储存时用的批次号,同一个货品在一个料格要换一次批次号用顺序号解决
    3# Get_StorageCache_Loc  获取输送线存储缓存区货位
 
    5# Get_Usable_Lane       获取可用巷道(有些堆垛机故障后这些巷道会不可用)
    6# Get_Station_ExtData   获取机台的扩展数据(里面有mac地址,货位,去向库区等)
    7# Get_Usable_Lane       获取机台的货位
    8# Get_EmptyBox_Loc      ***获取指定库区内的空料箱
    9# Organize_CNTR_PostProcess 组盘入库后处理
]]
 
--//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- 根据物料的长中短尺寸计算适用料箱
-- long 长边 middle 中边 short 短边
-- 返回参数:
-- nRet 0 成功 非0失败 
-- box_style
function jx_base.GetBoxStyle( strLuaDEID, long, middle, short )
    local nRet, strRetInfo
 
    -- 校验输入参数
    if ( long == nil or long <= 0 or long >= 60) then return 1, "长边值不符合规范! 长边不能超过 60 " end
    if ( middle == nil or middle <= 0 or middle >= 40) then return 1, "中边值不符合规范! 中边不能超过 40 " end
    if ( short == nil or short <= 0 or short >= 30) then return 1, "短边边值不符合规范! 短边不能超 30 " end
    if ( middle > long or short > middle ) then return 1, "长中短边顺序不对!" end
 
    -- 计算料箱型号
    if ( long < 20 ) then 
        return 0, '["A","B","C","D","E"]'
    elseif ( long >= 20 and long < 30) then
        if ( middle < 20 ) then
            return 0, '["A","B","C","D","E"]'
        elseif ( middle >= 20 and middle < 30 ) then 
            if ( short < 20 ) then 
                return 0, '["A","B","C","D"]'
            else
                return 0, '["A","B"]'
            end
        end
    elseif ( long >= 30 and long < 40) then
        if ( middle < 20 ) then
            return 0, '["A","B","C"]'
        elseif ( middle >= 20 and middle < 30 ) then 
            if ( short < 20 ) then 
                return 0, '["A","B","C"]'
            else
                return 0, '["A","B"]'
            end
        elseif ( middle >= 30 and middle < 40 ) then 
            return 0, '["A"]'
        end        
    elseif ( long >= 40 and long < 60) then
        return 0, '["A"]'
    end
    return 1, "长边参数不对!"
end
--//////////////////////////////////////////////////////////////////////////
-- 生成货品储存时用的批次号,同一个货品在一个料格要换一次批次号用顺序号解决
function jx_base.Generate_Batch_No( item_code )
    local nRet, strRetInfo
 
    if ( item_code == '' or item_code == nil ) then return 1, "generate_batch_no 输入参数必须有值" end
    local strCode = ''
    local strHeader = item_code..'_'..os.date("%y%m%d")..'-'
    nRet, strCode = mobox.getSerialNumber( "入库批次", strHeader, 3 )  
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1),  '申请入库批次号失败!'..strCode ) end
    local seg = lua.split( strCode, "_" )
    return 0, seg[2]
end
 
--[[
    获取 area_code 库区里可使用的堆垛机设备所在的巷道
--]]
function jx_base.Get_Usable_Lane( strLuaDEID, area_code )
    local nRet, strRetInfo
 
    if ( area_code == nil or area_code == '') then
        return 1, "jx_base.Get_Usable_Lane 输入参数 area_code不能为空!"
    end
    local stacker_dev = wms_base.Get_sConst( strLuaDEID, area_code.."-堆垛机设备" )
end
 
-- 获取 ‘机台-XX’ 常量并且解析成一个 站台扩展属性的 table
function jx_base.Get_Station_ExtData( strLuaDEID, station_code )
    if ( station_code == nil or station_code == '') then
        return 1, "jx_base.Get_Station_ExtData 函数中 station_code 不能为空!"
    end
    -- 通过盘点站台获取出库终点货位
    local station_info = wms_base.Get_sConst( strLuaDEID, "机台-"..station_code, 1 )
    if ( station_info == '' ) then
        return 1, "机台-"..station_code.." 常量中没有定义信息, 或没定义这个机台常量!" 
    end
    local staion_attr = json.decode( station_info )   
 
    return 0, staion_attr
end
 
-- 获取 ‘机台-XX’ 所在货位
function jx_base.Get_Station_Loc( strLuaDEID, station_code )
    local nRet, station_data
    nRet, station_data = jx_base.Get_Station_ExtData( strLuaDEID, station_code )
    if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), station_data ) end
    return station_data.loc_code
end
 
--[[
    获取某个库区内设备的状态(在JX项目中特指库区内的堆垛机状态)
    输入参数:
        area_code   -- 库区编码
        station -- 入库时的扫码点,可以不输入
 
    返回参数:
        nRet = 0 成功 非零 失败
        stacker_dev = [{"dev_no"::xxx",lane:1,enable:0/1,cntr_num:0}]
        -- cntr_num 巷道入库接驳位容器数量
        -- lane 巷道号
        -- dev_no 堆垛机号
--]]
function jx_base.Get_stacker_dev_state( strLuaDEID, area_code, station )
    local nRet, strRetInfo
    if ( station == nil ) then station = '' end
 
    -- 获取 area_code 库区堆垛机设备工作状态,确定哪些巷道可以使用
    strRetInfo = wms_base.Get_sConst( strLuaDEID, "库区"..area_code.."-堆垛机设备" )
    if ( strRetInfo == '') then
        return 1, "常量'库区"..area_code.."-堆垛机设备' 不能为空! "
    end
    --[{"dev_no"::xxx",lane:1,enable:false/true}]
    local stacker_dev = json.decode( strRetInfo )
    local n, m
    local dev_codes = ''
    for n = 1, #stacker_dev do
        dev_codes = dev_codes..stacker_dev[n].dev_no..","
    end
    if ( dev_codes == '') then
        return 1, "库区'"..area_code.."'没有定义设备!"
    end
    dev_codes = lua.trim_laster_char( dev_codes )
 
    -- 调用WCS接口获取设备状态
    -- [{"DVC_NO":"TC21","IS_USE":0/1,"CON_NUM":1},...]    
    nRet, dev_state =  jx_api.JW_DVC_State( strLuaDEID, dev_codes, station )
    if ( nRet ~= 0 ) then return nRet, dev_state end
    local find
    for n = 1, #stacker_dev do
        find = false
        for m = 1, #dev_state do
            if ( stacker_dev[n].dev_no == dev_state[m].DVC_NO ) then
                find = true
                stacker_dev[n].enable = lua.Get_NumAttrValue( dev_state[m].IS_USE )
                stacker_dev[n].cntr_num = lua.Get_NumAttrValue( dev_state[m].CON_NUM )
                break
            end
        end
        if ( find == false ) then
            return 1, "WCS返回的设备状态中没有编码='"..stacker_dev[n].dev_no.."'的设备状态!"
        end
    end
    return 0, stacker_dev
end
 
-- 获取库区库可用的堆垛机巷道号 -- 1, 2, 4
function jx_base.Get_Available_Lane( strLuaDEID, area_code )
    local nRet, stacker_dev
 
    nRet, stacker_dev = jx_base.Get_stacker_dev_state( strLuaDEID, area_code )
    if ( nRet ~= 0 ) then return nRet, stacker_dev end
 
    local n
    local lane = ''
    for n = 1, #stacker_dev do
        if ( stacker_dev[n].enable == 1 ) then
            lane = lane..stacker_dev[n].lane..","
        end
    end
    lane = lua.trim_laster_char( lane )
    return 0, lane
end
 
--//////////////////////////////////////////////////////////////////////////
local function get_lane_connect_loc( lane_connect_loc_set, lane_no )
    local n
 
    for n = 1, #lane_connect_loc_set do
        if ( lane_connect_loc_set[n].lane == lane_no ) then
            return lane_connect_loc_set[n]
        end
    end
    return ''
end
 
-- 排序
local function roadway_loc_sort( loc1, loc2 )
    if ( loc1.abc_cls < loc2.abc_cls ) then
        return true
    elseif ( loc1.abc_cls == loc2.abc_cls ) then
        -- 距离入口近的优先
        return loc1.weight < loc2.weight
    end
    return false
end
 
-- 巷道任务+空料箱排序
local function lane_list_sort( loc1, loc2 )
    if ( loc1.abc_cls < loc2.abc_cls ) then
        return true
    elseif ( loc1.abc_cls == loc2.abc_cls ) then
        -- 巷道空位多优先
        return loc1.roadway_empty_num > loc2.roadway_empty_num
    end
    return false
end
 
local function task_num_abc_cls( lane_list )
    local max_task_num = 0
    local n, nCount
 
    nCount = #lane_list
    if ( 0 == nCount ) then return 0 end 
    for n = 1, nCount do
        if ( lane_list[n].task_num > max_task_num ) then
            max_task_num = lane_list[n].task_num
        end
    end
    if ( max_task_num <= 3 ) then
        for n = 1, nCount do
            if ( lane_list[n].task_num == 0 ) then
                lane_list[n].abc_cls = "A"
            elseif ( lane_list[n].task_num >= 2 ) then
                lane_list[n].abc_cls = "C"
            else
                lane_list[n].abc_cls = "B"
            end 
        end           
    else
        for n = 1, nCount do
            if ( lane_list[n].task_num == 0 ) then
                lane_list[n].abc_cls = "A"
            elseif ( lane_list[n].task_num < max_task_num/2 ) then
                lane_list[n].abc_cls = "B"
            else
                lane_list[n].abc_cls = "C"
            end
        end
    end
    table.sort( lane_list, lane_list_sort )
end
 
-- 计算料箱库入库区货位(任务均衡),考虑巷道货位均衡, 基本原则是: 巷道任务少优先,巷道空货位多的优先
-- 返回入库缓存区货位和巷道内存储货位(2个货位)
-- V2.0 HAN 20241120 根据站台位置获取相应的 堆垛机入库缓存区,增加一个站台编码 station
function jx_base.Get_StorageCache_Loc( strLuaDEID, station )
    local nRet, strRetInfo
    local task_type = wms_base.Get_nConst( strLuaDEID, "任务类型-输送线入库搬运" )
 
    local station_attr
    --[[
        station_attr = {"loc_code":"S-A","entry_area_name":"A","code":"A","factory":"0001","storage_area":"K2","entry_area_code":"K4","mac":"A"}
        entry_area -- 入库接驳区  storage_area -- 存储区 loc_orde = 0 根据列顺序 1 -- 倒序
    ]]
    nRet, station_attr = jx_base.Get_Station_ExtData( strLuaDEID, station )    
    if ( nRet ~= 0 ) then return 2, station_attr end
    local loc_order = lua.Get_NumAttrValue( station_attr.loc_order )
 
    -- 站台指定的入库接驳区
    local storage_cache_arae = lua.Get_StrAttrValue( station_attr.entry_area_code )
    if ( storage_cache_arae == '') then
        return 2, "'机台-"..station.."'没有定义入库口区域编码信息!"
    end
 
    -- 获取入库接驳区货位的使用情况
    local strCondition = "C_ENABLE = 'Y' AND S_AREA_CODE = '"..storage_cache_arae.."'"
    local strOrder = "S_CODE"
    local loc_objs
    local lane_connect_loc_set = {}
    nRet, loc_objs = m3.QueryDataObject( strLuaDEID, "Location", strCondition, strOrder )
    if (nRet ~= 0) then lua.Error( strLuaDEID, debug.getinfo(1), "获取【Location】信息失败! " .. loc_objs ) end
 
     for n = 1, #loc_objs do
        obj_attrs = m3.KeyValueAttrsToObjAttr(loc_objs[n].attrs)
        -- 这些货位里把这个货位对应的巷道信息保存在 S_EXT_ATTR
        roadway = lua.StrToNumber( obj_attrs.S_EXT_ATTR )
        if ( roadway <= 0 or roadway > 4 ) then
            return 2, "输送线入库缓存位'"..obj_attrs.S_CODE.."'没有定义对应的巷道号或巷道号不合法"
        end  
        local connect_loc = {
            lane = roadway,
            loc_code = obj_attrs.S_CODE,
            --capacity = lua.StrToNumber( obj_attrs.N_CAPACITY ),
            --cur_num = lua.StrToNumber( obj_attrs.N_CURRENT_NUM ),            
        } 
        table.insert( lane_connect_loc_set, connect_loc )     
    end
 
    lua.Debug( strLuaDEID, debug.getinfo(1), "jx_base.Get_StorageCache_Loc-->lane_connect_loc_set", lane_connect_loc_set)
 
    -- 站台指定的入库存储区
    local storage_area = lua.Get_StrAttrValue( station_attr.storage_area )
    if ( storage_area == '') then
        return 2, "'机台-"..station.."'没有定义存储区编码信息!"
    end    
    -- 获取库区 storage_area 内设备状态
    local stacker_dev
    nRet, stacker_dev = jx_base.Get_stacker_dev_state( strLuaDEID, storage_area, station )
    if ( nRet ~= 0 ) then return 2, stacker_dev end
 
    lua.Debug( strLuaDEID, debug.getinfo(1), "jx_base.Get_StorageCache_Loc-->stacker_dev", stacker_dev)
 
    local n, m
    local storage_cache_list = {}
    local obj_attrs
    local task_num, max_task_num, empty_loc_num
    local loc_count = #loc_objs
    local loc_index
    local connect_loc
 
    -- 获取立库巷道口货位的基本信息,保存到 storage_cache_list
    max_task_num = 0
    for n = 1, #stacker_dev do
        -- 获取设备所在巷道
        roadway = stacker_dev[n].lane
        if ( roadway <= 0 or roadway > 4 ) then
            return 2, "库区"..storage_area.."中的巷道号只能在1~4!"
        end
        -- enable = 1 表示巷道堆垛机可用
        if ( stacker_dev[n].enable == 1 ) then
            task_num = stacker_dev[n].cntr_num
            if ( task_num > max_task_num ) then
                max_task_num =  task_num
            end
 
            -- 查询货位启用、空货位、在本巷道、没被锁的货位数量
            strCondition = "N_ROADWAY = "..roadway.." AND S_AREA_CODE = '"..storage_area.."' AND C_ENABLE = 'Y' AND N_CURRENT_NUM = 0 AND  N_LOCK_STATE = 0"
            nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "Location", strCondition )
            if ( nRet ~= 0 ) then return nRet, strRetInfo end 
            empty_loc_num = lua.StrToNumber( strRetInfo )  
            if ( empty_loc_num > 0 ) then
                -- 获取该巷道的 入库接驳位
                loc_index = 0
                for m = 1, #lane_connect_loc_set do
                    if ( lane_connect_loc_set[m].lane == roadway ) then
                        loc_index = m
                        break
                    end
                end
                if ( loc_index ~= 0 ) then
                    local cache_loc = {
                        loc_code = lane_connect_loc_set[loc_index].loc_code,
                        roadway = roadway,
                        task_num = task_num,                                                -- 任务数量
                        roadway_empty_num = empty_loc_num,                              -- 对应巷道的空货位数量
                        abc_cls = ""                                                        -- ABC 分类
                    }
                    table.insert( storage_cache_list, cache_loc )
                end
            end
        end
    end    
 
    lua.Debug( strLuaDEID, debug.getinfo(1), "jx_base.Get_StorageCache_Loc-->storage_cache_list1", storage_cache_list)
 
    -- 计算 各入库缓存区的 任务数量 ABC 分类值 没有任务的为 A,任务数大于 0 小于 max_task_num/2 的为B,其余为 C
    local nCount = #storage_cache_list
    if ( nCount == 0 ) then
        return 1, "系统无法分配货位!"
    end
 
    -- 排序 abc_cls 按ABC排序,同样等级的abc_cls 根据roadway_empty_num排序,空货位多的在前面
    task_num_abc_cls( storage_cache_list )
 
    lua.Debug( strLuaDEID, debug.getinfo(1), "jx_base.Get_StorageCache_Loc-->storage_cache_list2", storage_cache_list)
 
    -- 获取巷道内空货位
    local storage_arae = wms_base.Get_sConst( strLuaDEID, "料箱库存储区" )
    local ret_loc
 
    -- strLuaDEID, 库区, 巷道号, 货位排序方法, 顺序/倒序, 附加条件, 取货位数量
    nRet, ret_loc = wms_alg.Get_Roadway_Empty_Loc( strLuaDEID, storage_arae, storage_cache_list[1].roadway, 0, loc_order, "", 1 )
 
    if ( nRet ~= 0 ) then
        return 1, "计算巷道"..storage_cache_list[1].roadway.."内货位失败!"..ret_loc
    end       
    nRet, loc = wms_wh.GetLocInfo( ret_loc.loc_code )
    if ( nRet ~= 0 ) then 
        return 1, "获取货位'"..ret_loc.loc_code.."'信息失败! "..loc
    end 
 
    -- 返回 立库巷道口入库货位,巷道内存储货位
    return 0, storage_cache_list[1].loc_code, ret_loc.loc_code
end
 
-- 获取一个空料箱货位
local function get_empty_box_location( strLuaDEID, trans_id, area_code, lane_list, box_type )
    local nRet, strRetInfo, n
    local nCount = #lane_list
 
    -- 对巷道内任务数量进行重新分类
    task_num_abc_cls( lane_list )
    -- 取排列第一的exit_loc_list 所在巷道的空料箱
    lua.Debug( strLuaDEID, debug.getinfo(1), "jx_base.Get_EmptyBox_Loc--->lane_list1", lane_list )
    local roadway = 0
    for n = 1, nCount do
        if ( lane_list[n].roadway_empty_num > 0 ) then
            roadway = lane_list[n].roadway
            break
        end
    end
    if ( roadway == 0 ) then return 1, "库区'"..area_code.."'没有空料箱!"  end
 
    -- 要联表查询的表
    local strTable = "TN_Container a LEFT JOIN TN_Loc_Container b ON a.S_CODE = b.S_CNTR_CODE" 
    -- 要查询的属性
    local strAttrs = "a.S_CODE, b.S_LOC_CODE" 
    
    -- 从指定的巷道获取巷道内 巨星空料箱,根据位置进行排序
    strCondition =  "a.N_EMPTY_FULL = 0 AND a.N_TYPE = "..box_type..
                    "AND a.S_CODE IN ( select S_CNTR_CODE from TN_Loc_Container with (NOLOCK) where S_LOC_CODE IN "..
                    "(select S_CODE from TN_Location with (NOLOCK) where S_AREA_CODE = '"..area_code.."' AND N_ROADWAY = "..roadway.."))"..
                    "AND a.N_LOCK_STATE = 0 AND a.C_ENABLE = 'Y'"
    strOrder = ""
    nRet, strRetInfo = mobox.queryMultiTable(strLuaDEID, strAttrs, strTable, 1, strCondition, strOrder )
 
    if (nRet ~= 0) then lua.Error( strLuaDEID, debug.getinfo(1),"QueryDataObject失败!"..strRetInfo ) end
    if ( strRetInfo == '' ) then return 1, "库区'"..area_code.."'没有空料箱!"  end
    local ret_attr = json.decode(strRetInfo)
 
    -- 巷道任务+1,这种批量分配的料箱还没创建任务,所以查Task是查不到巷道任务数量的
    for n = 1, nCount do
        if ( lane_list[n].roadway  == roadway ) then
            lane_list[n].task_num = lane_list[n].task_num + 1
            lane_list[n].roadway_empty_num = lane_list[n].roadway_empty_num - 1
            break
        end
    end    
    lua.Debug( strLuaDEID, debug.getinfo(1), "jx_base.Get_EmptyBox_Loc--->lane_list2", lane_list )
 
    -- 返回: 巷道, 容器编码、货位编码
    local cntr_code = ret_attr[1][1]
    local loc_code = ret_attr[1][2]
    nRet, strRetInfo = wms_cntr.Lock( strLuaDEID, cntr_code, 2, trans_id )
    if ( nRet ~= 0 ) then return 2, strRetInfo end
    return 0, roadway, cntr_code, loc_code    
end
 
--[[
    【关键函数】
        空料箱货位计算, 基本原则是: 巷道任务少优先,巷道空料箱多的优先, 不锁容器
    参数说明:
        area_code -- 存储库区编码 
        trans_id -- 事务标识
        box_type = 2 是给巨星WMS用的容器, 3 -- 是料箱给巨沃用的料箱
        num -- 空料箱的个数缺省=1
    返回参数: 
        nRet, roadway -- 巷道 cntr_code/料箱号, loc_code/货位号
]] 
 
function jx_base.Get_EmptyBox_Loc( strLuaDEID, trans_id, area_code, box_type, num )
    local nRet, strRetInfo
 
    if ( area_code == nil or area_code == '') then
        return 2, "jx_base.Get_EmptyBox_Loc 中参数 area_code 必须有值! "
    end
    if ( box_type == nil ) then
        return 2, "jx_base.Get_EmptyBox_Loc 中参数 box_type 必须有值! "
    end
    if ( num == nil ) then num = 1 end
 
    -- 获取库区内设备状态
    nRet, stacker_dev = jx_base.Get_stacker_dev_state( strLuaDEID, area_code )
    if ( nRet ~= 0 ) then return 2, stacker_dev end
 
    local n, roadway
    local lane_list = {}         -- 巷道
    local task_num, empty_box_num
 
    -- 获取立库巷道口货位的基本信息,保存到 storage_cache_list
    for n = 1, #stacker_dev do
        -- 获取设备所在巷道
        roadway = stacker_dev[n].lane
        if ( roadway <= 0 or roadway > 4 ) then
            return 2, "库区"..area_code.."中的巷道号只能在1~4!"
        end
        if ( stacker_dev[n].enable == 1 ) then
            -- 获取该巷道未完成的任务数量
            -- N_B_STATE < 3 表示任务的状态为 0等待/1已推送/2执行中, 任务的 area_code  的巷道 roadway
            -- 任务包括进出的
            strCondition = "N_B_STATE < 3 AND ( N_START_LANE = "..roadway.." or N_END_LANE = "..roadway..")"..
                           "AND ( S_START_AREA = '"..area_code.."' or S_END_AREA = '"..area_code.."')"
 
            nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "Task", strCondition )
            if ( nRet ~= 0 ) then return nRet, strRetInfo end 
            task_num = lua.StrToNumber( strRetInfo )    
 
            -- 查询容器为空,满足类型条件,在某库区,某巷道
            strCondition = "N_EMPTY_FULL = 0 AND N_TYPE = "..box_type..
                           "AND S_CODE IN ( select S_CNTR_CODE from TN_Loc_Container with (NOLOCK) where S_LOC_CODE IN "..
                           "(select S_CODE from TN_Location with (NOLOCK) where S_AREA_CODE = '"..area_code.."' AND N_ROADWAY = "..roadway.."))"..
                           "AND N_LOCK_STATE = 0 AND C_ENABLE = 'Y'"
 
            nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "Container", strCondition )
            if ( nRet ~= 0 ) then return nRet, strRetInfo end 
            empty_box_num = lua.StrToNumber( strRetInfo )  
 
            if ( empty_box_num > 0 ) then
                local lane = {
                    roadway = roadway,
                    task_num = task_num,                                                -- 任务数量
                    roadway_empty_num = empty_box_num,                                  -- 对应巷道的空料箱数量
                    abc_cls = ""                                                        -- ABC 分类
                }
                table.insert( lane_list, lane )
            end
        end
    end    
 
    -- 计算 各入库缓存区的 任务数量 ABC 分类值 没有任务的为 A,任务数大于 0 小于 max_task_num/2 的为B,其余为 C
    local nCount = #lane_list
    if ( nCount == 0 ) then
        return 1, "库区'"..area_code.."'无法出空料箱!"
    end
 
    local empty_box_list = {}
    for n = 1, num do
        nRet, roadway, cntr_code, loc_code = get_empty_box_location( strLuaDEID, trans_id, area_code, lane_list, box_type )
        if ( nRet == 0 ) then
            local empty_box = {
                roadway = roadway, cntr_code = cntr_code, loc_code = loc_code
            }
            table.insert( empty_box_list, empty_box )
        else
            if ( nRet > 1 )  then return 2, roadway end
        end
    end
    return 0, empty_box_list
 
end
 
 
-- 输送线入库任务完成,系统自动创建一个堆垛机入库任务
function jx_base.ConveyorLineStorage_Task_Finish( strLuaDEID, task, operation )
    local nRet, strRetInfo
    
    -- 创建堆垛机入库任务
    local new_task = m3.AllocObject(strLuaDEID,"Task")
 
    new_task.op_code = operation.code                            -- 作业编码
    new_task.source_sys = operation.source_sys                   -- 来源系统
    new_task.op_name = operation.op_def_name
    new_task.factory = operation.factory                         -- 工厂
    new_task.bs_type = operation.bs_type                        
    new_task.bs_no = operation.bs_no  
    new_task.type = wms_base.Get_nConst(strLuaDEID, "任务类型-堆垛机入库搬运") 
    new_task.cntr_code = task.cntr_code
    -- 起点
    new_task.start_wh_code = task.end_wh_code
    new_task.start_area_code  = task.end_area_code
    new_task.start_loc_code  = task.end_loc_code
    -- 终点
    new_task.end_wh_code  = operation.end_wh_code
    new_task.end_area_code  = operation.end_area_code
    new_task.end_loc_code  = operation.end_loc_code
    new_task.schedule_type = wms_base.Get_nConst(strLuaDEID, "调度类型-堆垛机") -- 设置调度类型
 
    nRet, new_task = m3.CreateDataObj(strLuaDEID, new_task)
    if (nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1),"创建堆垛机任务失败!"..new_task) end
 
    return 0
end
 
--[[
    根据站台位置创建一个入库作业,处理以下事情
        -- 从立库中获取一个空货位
        -- 创建一个作业
        -- 给获取的空货位加入库锁
 
    输入参数:
        -- source_sys   来源系统
        -- station      站台位置
        -- cntr_code    容器号
        -- op_def_name  作业定义名称
        -- ext_info     扩展参数{orgc_no,bs_type,bs_no,cc_no,dc_no}
                        orgc_no 组盘用 cc_no 盘点 dc_no 分拣
    返回:
        nRet
        operation
]] 
 
function jx_base.Create_StorageOperation( strLuaDEID, source_sys, station, cntr_code, op_def_name, ext_info )
    local nRet, strRetInfo
    local loc_code = jx_base.Get_Station_Loc( strLuaDEID, station )
    local loc
    nRet, loc = wms_wh.GetLocInfo( loc_code )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), '获取货位信息失败! '..loc_code ) end  
 
    local operation = m3.AllocObject(strLuaDEID,"Operation")
    operation.source_sys = source_sys
    operation.start_wh_code = loc.wh_code
    operation.start_area_code = loc.area_code
    operation.start_loc_code = loc_code
 
    -- 计算料箱入库货位,首先计算到哪个输送线入库缓存区
    local str_end_loc_code, roadway_in_loc
    nRet, roadway_in_loc, str_end_loc_code = jx_base.Get_StorageCache_Loc( strLuaDEID, station )
    if ( nRet ~= 0 ) then
        if ( nRet == 1 ) then
            return 1, "jx_base.Get_StorageCache_Loc 失败!"..roadway_in_loc
        end
        lua.Error( strLuaDEID, debug.getinfo(1), roadway_in_loc )
    end
    local end_loc
    nRet, end_loc = wms_wh.GetLocInfo( str_end_loc_code )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), '获取货位信息失败! '..str_end_loc_code ) end  
    operation.end_wh_code = end_loc.wh_code
    operation.end_area_code = end_loc.area_code
    operation.end_loc_code = str_end_loc_code
    operation.ext_data = roadway_in_loc             -- 巷道口入库缓存货位
 
    operation.op_type = wms_base.Get_nConst(strLuaDEID, "作业类型-入库")
    operation.op_def_name = op_def_name
    operation.cntr_code = cntr_code
    
    if ( ext_info ~= nil and ext_info ~= '') then
        if ( ext_info.orgc_no ~= nil ) then
            -- 有组盘的(先空料箱呼出)
            operation.orgc_no = ext_info.orgc_no
        end
        if ( ext_info.cc_no ~= nil ) then
            -- 盘点回库
            operation.cc_no = ext_info.cc_no
        end       
        if ( ext_info.dc_no ~= nil ) then
            -- 盘点回库
            operation.dc_no = ext_info.dc_no
        end           
        operation.bs_type = lua.Get_StrAttrValue( ext_info.bs_type )
        operation.bs_no = lua.Get_StrAttrValue( ext_info.bs_no )
    end
 
    nRet, operation = m3.CreateDataObj( strLuaDEID, operation )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), '创建【作业】失败!'..operation ) end 
   
    return 0, operation
end
 
--[[
    得到当前登录人员的账号信息,并且获取登录人员的PC MAC地址,根据这个地址获取绑定的站台
    返回一个json table
    nRet, ret_value { login, user_name, mac, station }
]]
function jx_base.Get_CurLoginUserInfo( strLuaDEID )
    local nRet, strRetInfo
    local ret_value = {}
 
    local strUserLogin, strUserName
    nRet, strUserLogin, strUserName = mobox.getCurUserInfo( strLuaDEID )
    if ( nRet ~= 0 ) then return 2, "获取当前操作人员信息失败! "..strUserLogin end
    
    ret_value.login = strUserLogin
    ret_value.user_name = strUserName
    ret_value.mac = ""
    ret_value.station = ""
 
    -- 获取当前登录账号的电脑MAC地址(如果在MBC上登录是可以获取的)
    nRet, strRetInfo = mobox.getCacheValue( strUserLogin, "__user_terminal__" )
    if ( nRet == 0 ) then
        local user_terminal = json.decode( strRetInfo ) 
        ret_value.mac = lua.Get_StrAttrValue( user_terminal.mac )
 
        -- 根据 MAC-站台 绑定常量获取 站台
        if ( ret_value.mac ~= '' ) then
            ret_value.station = wms_base.Get_sConst( strLuaDEID, ret_value.mac)
        end
    end
 
   return 0, ret_value
  
end
 
--[[
    检查一下 orgc_no 下面的 detail 是否已经完成处理完成,如果完成处理 创建一个 入库作业
    返回值:
        action
]]
function jx_base.Organize_CNTR_PostProcess( strLuaDEID, orgc_no, station, cntr_code, bs_no )
    local nRet, strRetInfo
 
    -- 检查一下当前料箱的入库任务是否已经全部完成,如果完成就创建一个【货品入库】作业
    -- N_B_STATE = 1 表示科执行的入库任务
    local strCondition = "S_CNTR_CODE = '"..cntr_code.."' AND N_B_STATE = 1 AND S_BS_NO = '"..bs_no.."'"       
    nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "Organize_CNTR_Detail", strCondition )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), strRetInfo ) end 
    local nCount = lua.StrToNumber( strRetInfo )  
 
    local operation = {}    
    local strUpdateSql
    local action = {
        {
            action_type = "refresh_master_panel",
            value = {
                sub_page = {"当前任务", "未执行任务"}
            }
        }
    } 
 
    if ( nCount == 0 ) then
        -- ***
        -- 发现过相同有相同组盘的入库作业,这是严重的数据错误,这里加一个判断
        strCondition = "S_ORGC_NO = '"..orgc_no.."' AND N_TYPE = 1"
        nRet, strRetInfo = mobox.existThisData( strLuaDEID, "Operation", strCondition )
        if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), strRetInfo )  end
        -- 如果该车辆编码的动作已经在队列,返回,不做处理
        if ( strRetInfo == "yes" ) then 
            lua.Error( strLuaDEID, debug.getinfo(1), "容器号'"..cntr_code.."'不能重复创建货品入库作业!" )
        end        
 
        -- 创建【货品入库】作业. 【组盘容器】状态改为3, CG_Detail 加【组盘容器明细】中的内容
        local ext_info = {}
        ext_info.orgc_no = orgc_no
        nRet, operation = jx_base.Create_StorageOperation( strLuaDEID, "巨沃", station, cntr_code, "货品入库", ext_info )
        if ( nRet ~= 0 ) then
            mobox.setInfo( strLuaDEID, operation )
            -- 3 组盘完成,回库出现问题
            strUpdateSql = "N_B_STATE = 3"
            strCondition = "S_ORGC_NO = '"..orgc_no.."'"
            nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Organize_Container", strCondition, strUpdateSql )
            if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), "更新【组盘容器】信息失败!"..strRetInfo ) end   
            return action
        end            
 
        --【组盘容器】+ 回库作业号, 状态 = 4 (回库)
        local strUpdateSql = "S_BACK_OP_NO = '"..operation.code.."', N_B_STATE = 4"
        strCondition = "S_ORGC_NO = '"..orgc_no.."'"
        nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Organize_Container", strCondition, strUpdateSql )
        if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), "更新【组盘容器】信息失败!"..strRetInfo ) end   
 
        --容器中的【CG_Detail】新增,把【组盘容器明细】中的内容加入
        nRet, strRetInfo = wms_cntr.Add_CG_Detail_ByOrgcDetail( strLuaDEID, cntr_code, orgc_no )
        if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), 'wms_cntr.Add_CG_Detail_ByOrgcDetail!'..strRetInfo ) end 
         
        --重置料箱信息
        local container
        nRet, container = wms_cntr.GetInfo( strLuaDEID, cntr_code )
        if (nRet ~= 0) then lua.Error( strLuaDEID, debug.getinfo(1), "获取【容器】信息失败! " .. container) end        
        nRet, strRetInfo = wms_cntr.Reset( strLuaDEID, container )
        if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), strRetInfo ) end 
        
        --入库作业产生需要对入库作业的终点获取加入库锁
        nRet, strRetInfo = wms.wms_LockLocation(strLuaDEID, operation.end_loc_code, wms_base.Get_nConst( strLuaDEID, "锁类型-入库锁" ),
                                                "", operation.code, operation.op_def_name )
        if (nRet ~= 0) then lua.Error( strLuaDEID, debug.getinfo(1), "wms_LockLocation 失败!"..strRetInfo ) end     
 
        action[2] = {
            action_type = "set_dlg_attr",
            value = {  
                        { attr = "UPC", value = "", enable = true, prompt = "请扫商品条码..."  },
                        { attr = "S_CNTR_CODE", value = "", enable = false },
                        { attr = "S_CELL_NO", value = "", enable = false },
                        { attr = "S_ITEM_CODE", F_QTY = "", enable = false },
                        { attr = "F_QTY", value = "", enable = false },
                        { attr = "F_ACT_QTY", value = "", enable = false },
                        { attr = "S_ITEM_NAME", value = "", enable = false }
                     }       
        }  
        action[3] = {
            action_type = "refresh_related_panel",
            value = {  
                        {
                            panel_name = "料格显示",
                            input_parameter = {
                                cell_no = ""
                            }
                        }  
                     }       
        } 
 
        action[4] = {
            action_type = "set_master_panel_cursor",
            value = { form_name = "3055 TOP VIEW", ctrl_id = "S_CNTR_CODE", value = ""}
        }                        
    end   
 
    return action  
end
 
 
--[[
    检查一下当前正在分拣的料箱容器 dc_no (配盘容器) 下面的 detail 是否已经完成处理完成,如果完成处理表示该容器分拣完成 创建一个 料箱入库
 
    返回值:
        action -- 
]]
function jx_base.Distribution_CNTR_PostProcess( strLuaDEID, parameter )
    local nRet, strRetInfo
    local strCondition
 
    -- 检查一下当前料箱的分拣出库任务是否已经全部完成,如果完成就创建一个【料箱h入库】作业
    -- N_B_STATE = 1 表示科执行的入库任务
    strCondition = "S_CNTR_CODE = '"..parameter.cntr_code.."' AND N_B_STATE = 1 AND S_BS_NO = '"..parameter.bs_no.."'"       
    nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "Distribution_CNTR_Detail", strCondition )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), strRetInfo ) end 
    local nCount = lua.StrToNumber( strRetInfo )  
 
    -- 当前的配盘中已经没有需要执行的出库任务
    local operation = {}   
    local action = {
        {
            action_type = "refresh_master_panel",
            value = {
                sub_page = {"当前任务", "未执行任务"}
            }
        }
    } 
 
    if ( nCount == 0 ) then
        -- 创建【料箱入库】作业. 【配盘容器】状态改为4(拣货完成), CG_Detail 减去【配盘容器明细】中的内容
        local ext_info = {dc_no = parameter.dc_no}
        nRet, operation = jx_base.Create_StorageOperation( strLuaDEID, "巨沃", parameter.station, parameter.cntr_code, "料箱入库", ext_info )
        if ( nRet ~= 0 ) then
            mobox.setInfo( strLuaDEID, operation )
            -- 9 分拣完成,回库出现问题
            strUpdateSql = "N_B_STATE = 9"
            strCondition = "S_DC_NO = '"..parameter.dc_no.."'"
            nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Distribution_CNTR", strCondition, strUpdateSql )
            if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), "更新【配盘】信息失败!"..strRetInfo ) end   
            return action
        end  
       
        -- 【配盘】+ 回库作业号, 状态 = 5 (回库)
        strUpdateSql = "S_BACK_OP_NO = '"..operation.code.."', N_B_STATE = 5"
        strCondition = "S_DC_NO = '"..parameter.dc_no.."'"
        nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Distribution_CNTR", strCondition, strUpdateSql )
        if ( nRet ~= 0 ) then  
            lua.Error( strLuaDEID, debug.getinfo(1), "更新【配盘】信息失败!"..strRetInfo ) 
        end 
        local distribution_cntr  
        nRet, distribution_cntr = m3.GetDataObjByCondition( strLuaDEID, "Distribution_CNTR", strCondition )
        if ( nRet ~= 0 ) then  
            lua.Error( strLuaDEID, debug.getinfo(1), "获取【配盘】信息失败!"..strRetInfo ) 
        end  
 
        --容器中的【CG_Detail】减【配盘明细】中的内容, 并且生成下架记录, 扣减量表信息
        nRet, strRetInfo = wms_out.Distribution_CNTR_Detail_PostProcess( strLuaDEID, parameter.wh_code, parameter.area_code, parameter.loc_code, parameter.dc_no )
        if ( nRet ~= 0 ) then 
            lua.Error( strLuaDEID, debug.getinfo(1), 'wms_out.Distribution_CNTR_Detail_PostProcess!'..strRetInfo ) 
        end 
 
        -- 增加一个后台进程对配盘进行处理,触发配盘明细中的 出库单是否可以完成
        local add_wfp = {
                            wfp_type = 1,
                            cls = "Distribution_CNTR",
                            obj_id = distribution_cntr.id,
                            obj_name = "配盘'"..parameter.dc_no.."'-->出库后处理",
                            trigger_event = "出库后处理"
                        }
        nRet, strRetInfo = m3.AddSysWFP( strLuaDEID, add_wfp )
        if ( nRet ~= 0 ) then 
            lua.Error( strLuaDEID, debug.getinfo(1), "AddSysWFP失败!"..strRetInfo )  
        end  
        -- 位置加入库锁
        nRet, strRetInfo = wms.wms_LockLocation(strLuaDEID, operation.end_loc_code, wms_base.Get_nConst( strLuaDEID, "锁类型-入库锁" ),
                                                "", operation.code, operation.op_def_name )
        if (nRet ~= 0) then lua.Error( strLuaDEID, debug.getinfo(1), "wms_LockLocation 失败!"..strRetInfo ) end            
 
        action[2] = {
            action_type = "set_dlg_attr",
            value = {  
                        { attr = "UPC", value = "", enable = true, prompt = "请扫商品条码..."  },
                        { attr = "Prompt", value = "请扫商品条码..." },
                        { attr = "S_CNTR_CODE", value = "", enable = false },
                        { attr = "S_CELL_NO", value = "", enable = false },
                        { attr = "S_ITEM_CODE", value = "", enable = false },
                        { attr = "F_QTY", value = "", enable = false },
                        { attr = "F_ACC_P_QTY", value = "", enable = false },
                        { attr = "S_ITEM_NAME", value = "", enable = false }
                     }       
        }  
        action[3] = {
            action_type = "refresh_related_panel",
            value = {  
                        {
                            panel_name = "料格显示",
                            input_parameter = {
                                cell_no = ""
                            }
                        },
                        {
                            panel_name = "拣料箱显示",
                            input_parameter = {
                                pick_box_code = "",      -- 拣料箱号  
                            }
                        }                          
                     }       
        } 
 
        action[4] = {
            action_type = "set_master_panel_cursor",
            value = { form_name = "3055 TOP VIEW", ctrl_id = "S_CNTR_CODE", value = ""}
        }           
    end 
    
    return action
end
 
--[[
    创建一个空料箱出库作业(组盘出)
    obj_attrs 组盘对象    
]]
function jx_base.CreateEmptyBoxOutOperation ( strLuaDEID, obj_attrs ) 
    local nRet, strRetInfo
    local msg
    local orgc_no = obj_attrs.S_ORGC_NO 
    local b_state = lua.Get_NumAttrValue( obj_attrs.N_B_STATE )
    local to_station = obj_attrs.S_STATION_NO
    local cntr_code = lua.Get_StrAttrValue( obj_attrs.S_CNTR_CODE )  
    local bs_type = lua.Get_StrAttrValue( obj_attrs.S_BS_TYPE )
    local bs_no = lua.Get_StrAttrValue( obj_attrs.S_BS_NO )
 
    -- 【组盘容器】数据对象属性判断,不合法的终止程序执行
    if ( b_state ~= 0 ) then
        msg = "组盘号'"..orgc_no.."'的状态不是未执行状态,不能启动料箱出库作业!"
        lua.Warning( strLuaDEID, debug.getinfo(1), msg )
        return 1, msg
    end
    if ( cntr_code == '' ) then
        msg = "组盘号'"..orgc_no.."'中的容器编码为空!"
        lua.Warning( strLuaDEID, debug.getinfo(1), msg )
        return 1, msg        
    end
 
    local from_loc_code = wms_wh.GetLocCodeByCNTR( strLuaDEID, cntr_code )
    if ( from_loc_code == nil or from_loc_code == '' ) then
        msg = "组盘号'"..orgc_no.."'中的容器'"..cntr_code.."'没有绑定货位!"
        return 1, msg
    end
 
    local from_loc
    nRet, from_loc = wms_wh.GetLocInfo( from_loc_code )
    if ( nRet ~= 0 ) then 
        return 1, '获取货位信息失败! '..loc_code
    end      
 
    -- 创建【料箱出库】作业
    -- 获取站点货位,站点货位定义在常量中
    local to_loc_code = jx_base.Get_Station_Loc( strLuaDEID, to_station )
    local to_loc
    nRet, to_loc = wms_wh.GetLocInfo( to_loc_code )
    if ( nRet ~= 0 ) then 
        return 1, '获取货位信息失败! '..to_loc
    end  
 
    local operation = m3.AllocObject(strLuaDEID,"Operation")
    operation.bs_state = 8                              -- 待启动前,这些作业有待后台脚本来设置为状态 0 
    operation.start_wh_code = from_loc.wh_code
    operation.start_area_code = from_loc.area_code
    operation.start_loc_code = from_loc_code
 
    operation.end_wh_code = to_loc.wh_code
    operation.end_area_code = to_loc.area_code
    operation.end_loc_code = to_loc_code
 
    operation.op_type = wms_base.Get_nConst(strLuaDEID, "作业类型-出库")
    operation.op_def_name = "料箱出库"
    operation.cntr_code = cntr_code
    operation.orgc_no = orgc_no
    operation.source_sys = "巨沃"
 
    nRet, operation = m3.CreateDataObj( strLuaDEID, operation )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), '创建【作业】失败!'..operation ) end  
 
    -- 更新【组盘容器】对象属性
    -- N_B_STATE = 1 表示组盘容器已经安排作业进行搬运
    local strUpdateSql = "S_FROM_LOC = '"..from_loc_code.."', N_B_STATE = 1, S_OUT_OP_NO = '"..operation.code.."'"
    strCondition = "S_ORGC_NO = '"..orgc_no.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Organize_Container", strCondition, strUpdateSql )
    if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), "更新【组盘容器】信息失败!"..strRetInfo ) end  
    
    -- 获取【组盘容器】的业务来源,设置业务来源的状态
    if  ( bs_type == "Inbound_Wave" and bs_no ~= '') then
        -- N_B_STATE = 2 表示入库波次已经开始作业
        strUpdateSql = "N_B_STATE = 2"
        strCondition = "S_WAVE_NO = '"..bs_no.."'"
        nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Inbound_Wave", strCondition, strUpdateSql )
        if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), "更新【入库波次】信息失败!"..strRetInfo ) end  
    end
 
    return 0
end
 
--[[ 
    创建一个配盘出库作业
    obj_attrs 配盘数据对象 obj_attrs.S_DC_NO,...
--]]
function jx_base.Create_Distribution_OutOperation ( strLuaDEID, obj_attrs ) 
    local nRet, strRetInfo
    local msg
    local dc_no = obj_attrs.S_DC_NO 
    local b_state = lua.Get_NumAttrValue( obj_attrs.N_B_STATE )
    local to_station = lua.Get_StrAttrValue( obj_attrs.S_STATION_NO )
    local cntr_code = obj_attrs.S_CNTR_CODE   
    local from_loc_code = lua.Get_StrAttrValue( obj_attrs.S_LOC_CODE )
    local to_loc_code = lua.Get_StrAttrValue( obj_attrs.S_EXIT_LOC_CODE )       -- 出库货位
    local bs_no = lua.Get_StrAttrValue( obj_attrs.S_BS_NO )                     -- 业务来源
    local bs_type = lua.Get_StrAttrValue( obj_attrs.S_BS_TYPE )                 -- 业务来源类型
 
    -- 【组盘容器】数据对象属性判断,不合法的终止程序执行
    -- b_state = 1 表示配盘完成
    if ( b_state ~= 1 ) then
        msg = "配盘号'"..dc_no.."'的状态不是未执行状态,不能启动配盘出库作业!"
        lua.Warning( strLuaDEID, debug.getinfo(1), msg )
        return 1, msg
    end
 
    if ( from_loc_code == '') then
        from_loc_code = wms_wh.GetLocCodeByCNTR( strLuaDEID, cntr_code )
        if ( from_loc_code == nil or from_loc_code == '' ) then
            return 1, "配盘号'"..orgc_no.."'中的容器'"..cntr_code.."'没有绑定货位!"
        end
    end
    
    local from_loc
    nRet, from_loc = wms_wh.GetLocInfo( from_loc_code )
    if ( nRet ~= 0 ) then 
        return 1, '获取货位信息失败! '..loc_code
    end      
 
    -- 创建【货品出库】作业. 【配盘】状态改为2(出库中), CG_Detail 加【组盘容器明细】中的内容
    -- 获取站点货位,站点货位定义在常量中
    if ( to_loc_code == '' ) then 
        if ( to_station == '') then
            return 1, "配盘号'"..dc_no.."'的配盘没定义出库站台或货位!"          
        end
        to_loc_code = jx_base.Get_Station_Loc( strLuaDEID, to_station )
    end
    local to_loc
    nRet, to_loc = wms_wh.GetLocInfo( to_loc_code )
    if ( nRet ~= 0 ) then 
        return 1, '获取货位信息失败! '..to_loc
    end  
 
    local operation = m3.AllocObject(strLuaDEID,"Operation")
    operation.bs_state = 8                              -- 待启动前,这些作业有待后台脚本来设置为状态 0 
    operation.start_wh_code = from_loc.wh_code
    operation.start_area_code = from_loc.area_code
    operation.start_loc_code = from_loc_code
 
    operation.end_wh_code = to_loc.wh_code
    operation.end_area_code = to_loc.area_code
    operation.end_loc_code = to_loc_code
 
    operation.op_type = wms_base.Get_nConst(strLuaDEID, "作业类型-出库")
    operation.op_def_name = "货品出库"
    operation.cntr_code = cntr_code
    operation.dc_no = dc_no
    operation.source_sys = "巨沃"
 
    operation.bs_type = bs_type
    operation.bs_no = bs_no
 
    nRet, operation = m3.CreateDataObj( strLuaDEID, operation )
    if ( nRet ~= 0 ) then return 1, '创建【作业】失败!'..operation end  
 
    -- 更新【配盘】对象属性
    -- N_B_STATE = 2 表示配盘容器已经安排作业进行搬运
    local strUpdateSql = "S_LOC_CODE = '"..from_loc_code.."', N_B_STATE = 2, S_OUT_OP_NO = '"..operation.code.."'"
    strCondition = "S_DC_NO = '"..dc_no.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Distribution_CNTR", strCondition, strUpdateSql )
    if ( nRet ~= 0 ) then  return 1, "更新【配盘】信息失败!"..strRetInfo  end   
    
    return 0
end
 
--[[ 
     从 wms_count.Get_CountPlan_QueryPanel_Condition 复制到巨星项目,大部分程序一样,只是在容器盘点时只盘点巨沃WMS的料箱
     即 容器的类型 N_TYPE = 3 是带料格的料箱,其他和标准函数一样
     获取新增 盘点计划 的查询面板中的查询条件, 注意这和定义的查询页面中的控件有关系,这个是标准功能里的面板获取查询条件的代码
 
     isSetQueryCondition = true 的情况下 容器查询 需要返回 base64
 
     返回 nRet, ret_condition_type, strCondition
     nRet = 0 成功, nRet = 1 条件不满足 nRet = 2 错误
     ret_condition_type = 0 返回的字符串是 纯SQL 
]]
function jx_base.Get_CountPlan_QueryPanel_Condition( strLuaDEID, isSetQueryCondition )
    local nRet, strRetInfo
    local wh_code
    local attrs = {}
    local ret_condition_type = 0        -- 非base64
 
    -- step1  获取当前查询输入面板中的 仓库编码/库区/物料编码/物料名称
    nRet, attrs = m3.GetSysInputParameter( strLuaDEID ) 
    if ( nRet ~= 0 )  then return 2, 0, "获取当前查询面板里的输入属性时失败! "..attrs end 
 
    local obj_attrs = m3.KeyValueAttrsToObjAttr(attrs)
    if (obj_attrs == nil) then return 2, 0, "询面板里的输入属性为空" end
    -- "N_TYPE","S_WH_CODE","Area","ItemCode", "ItemName"
    local check_type = tonumber(obj_attrs.N_TYPE)           -- 盘点类型
    local wh_code = obj_attrs.S_WH_CODE                     -- 仓库编码
    local area_code = obj_attrs.S_AREA_CODE                 -- 库区编码
    if  ( area_code == nil ) then area_code = '' end
 
    local strCondition =  ''
    local strOrder = ''
 
    if ( check_type == 0 )  then  return 1, 0, "必须选择一种盘点类型" end
    if ( wh_code == '' ) then  return 1, 0, "必须选择一个仓库"  end
 
    if ( check_type == wms_base.Get_nConst(strLuaDEID, "盘点类型-货品盘点")) then
        local item_code = obj_attrs.ItemCode                    -- 物料编码
        local item_name = obj_attrs.ItemName                    -- 物料名称
        local item_condition = ''
 
        if  ( item_code ~= '' and item_code ~= nil ) then
            item_condition = " AND S_ITEM_CODE like '%%"..item_code.."%%'"
        end
        if  ( item_name ~= '' and item_name ~= nil ) then
            item_condition = item_condition.." AND S_ITEM_NAME like '%%"..item_name.."%%' "
        end
        strOrder = 'S_ITEM_CODE'
        -- 如果是货物盘点
        if ( area_code == '') then
            -- 没有输入库区就直接查仓库量表
            strCondition = "F_QTY > 0 AND S_WH_CODE = '"..wh_code.."' "..item_condition
        else
            -- 有库区查库区量表
            strCondition = "F_QTY > 0 AND S_WH_CODE = '"..wh_code.."' AND S_AREA_CODE = '"..area_code.."' "..item_condition
        end
    else
        -- 如果是货位盘点
        local roadway = lua.StrToNumber(obj_attrs.Roadway)           -- 巷道
        local row = lua.StrToNumber(obj_attrs.Row)                   -- 排
        local col = lua.StrToNumber(obj_attrs.Col)                   -- 列
        local layer = lua.StrToNumber(obj_attrs.layer)               -- 层   
 
        strCondition = "S_WH_CODE = '"..wh_code.."'"
        if ( area_code ~= '') then
            strCondition = strCondition .. " AND S_AREA_CODE = '"..area_code.."' "
        end
        if ( roadway ~= 0 ) then
            strCondition = strCondition .. " AND N_ROADWAY = "..roadway
        end   
        if ( row ~= 0 ) then
            strCondition = strCondition .. " AND N_ROW = "..row
        end 
        if ( col ~= 0 ) then
            strCondition = strCondition .. " AND N_COL = "..col
        end 
        if ( layer ~= 0 ) then
            strCondition = strCondition .. " AND N_LAYER = "..layer
        end
 
        -- V2.0
        if ( check_type == wms_base.Get_nConst(strLuaDEID, "盘点类型-货位盘点")) then
            strCondition = strCondition.." AND N_CURRENT_NUM > 0"
        else
            -- 容器盘点
            -- 这里的查询条件带 In 不能用简单模式
            if ( isSetQueryCondition ) then
                local sql_condition = {
                    {
                        {
                            field = "C_ENABLE",
                            value = "Y",
                            op = "="
                        }
                    },
                    {
                        {
                            field = "N_LOCK_STATE",
                            value = "0",
                            op = "="
                        }
                    },
                    {
                        {
                            field = "N_TYPE",
                            value = "3",
                            op = "="
                        }
                    },    
                    {
                        {
                            field = "S_CODE",
                            value = "(select S_CNTR_CODE from TN_Loc_Container with (NOLOCK) where S_LOC_CODE in (select S_CODE from TN_Location with (NOLOCK) where "..strCondition.."))",
                            op = "in"
                        }
                    }                                             
                }
 
                lua.Debug( strLuaDEID, debug.getinfo(1), "sql_condition", sql_condition )
 
                nRet, strCondition = mobox.strToBase64( lua.table2str(sql_condition) )
 
                lua.Debug( strLuaDEID, debug.getinfo(1), "base64", strCondition )
 
                if ( nRet ~= 0 ) then return 2, 0, "strToBase64 失败: "..strCondition end 
                ret_condition_type = 1
            else
                strCondition = "C_ENABLE = 'Y' AND N_LOCK_STATE = 0 AND N_TYPE = 3 AND "..
                            "S_CODE in (select S_CNTR_CODE from TN_Loc_Container with (NOLOCK) where S_LOC_CODE in (select S_CODE from TN_Location with (NOLOCK) where "..strCondition.."))"
            end
        end
    end  
    return 0, ret_condition_type, strCondition
end
 
return  jx_base