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
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
--[[
    版本:     Version 1.0
    创建日期: 2025-1-27
    创建人:   HAN
 
    功能:
        和容器数据类(Container)相关的一些函数
 
        -- CreateVirtual  创建一个虚拟容器
        -- SetWeight      设置容器的重量
 
        -- GetInfo        获取容器信息
        -- InOperation    判断某个编号的容器是否被作业使用
        -- Exist          判断某个编号的容器是否存在
        -- Get_Loc_Container        获取货位中的容器编码
 
        -- SetLock        容器加锁/解锁
        -- Get_Container_Goods      获取容器货品明细
 
        -- Reset          重新设置容器里和CG_Detail,Cell箱格相关的属性     
 
        -- CanUsedInOperation   检查容器是否可用于作业搬运
    更改说明:
--]]
wms_inv  = require ("wms_inventory")
 
local wms_cntr = {_version = "0.1.1"}
--[[ 
    创建一个虚拟容器
--]]
function wms_cntr.CreateVirtual( strLuaDEID, container )
    local nRet, strRetInfo, strErr
    strErr = ''
 
    -- 生成容器编码
    local strCode = ''
    local strHeader = 'VC'..os.date("%y%m")..'-'
    nRet,strRetInfo = mobox.getSerialNumber( "虚拟容器", strHeader, 5 )  
    if ( nRet ~= 0 ) then
        strErr = '申请虚拟容器编码失败!'..strRetInfo
        return nRet, strErr
    end
    container.code = strRetInfo
 
    -- 获取创建容器时需要的属性
    nRet, container = m3.CreateDataObj( strLuaDEID, container )
    if ( nRet ~= 0 ) then
        return nRet, "CreateDataObj失败! "..container
    end
 
    return nRet, container
end
 
--[[
    从 data_object 中获取混箱属性加到 Container_Ext
--]]
function wms_cntr.Add_CNTR_ExtInfo( strLuaDEID, cntr_code, mixing_attrs, data_object )
    local cntr_ext_data = m3.AllocObject2( strLuaDEID, "Container_Ext" )
    
    cntr_ext_data.S_CNTR_CODE  = cntr_code 
    for _, mixing_attr in ipairs(mixing_attrs) do
        cntr_ext_data[mixing_attr] = data_object[mixing_attr]
    end
    nRet, cntr_ext_data = m3.CreateDataObj2( strLuaDEID, cntr_ext_data, 1 ) -- 1 表示已经存在就覆盖
    if ( nRet ~= 0 ) then 
        return 2, "创建[Container_Ext]失败!"..cntr_ext_data
    end 
    return 0
end
 
--[[
    设置容器的重量
    参数:
        cntr_code   容器编码
        fWeight     容器重量
-- ]]
function wms_cntr.SetWeight( strLuaDEID, cntr_code, fWeight )
    local nRet, strRetInfo, strErr
 
    if ( cntr_code == nil or  cntr_code == '' ) then return end
    if ( fWeight == nil or fWeight < 0) then return end
 
    local strSetAttr = "F_GOOD_WEIGHT = "..fWeight
    local strCondition = "S_CNTR_CODE = '"..cntr_code .. "'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container", strCondition, strSetAttr )
    if ( nRet ~= 0 ) then return nRet, strRetInfo end    
    return 0, "ok"
end
 
--[[
    获取容器信息
    参数:
        cntr_code   容器编码
-- ]]
function wms_cntr.GetInfo( strLuaDEID, cntr_code )
    if ( cntr_code == nil or cntr_code == '' ) then
        return 1, "WMS_Container_GetBaseInfo 容器编号不能为空!"
    end
 
    local nRet, strRetInfo, id
    local strCondition = "S_CODE = '"..cntr_code.."'"
    nRet, id, strRetInfo = mobox.getDataObjAttrByKeyAttr( strLuaDEID, "Container", strCondition )
 
    -- 返回1表示不存在
    if ( nRet == 1 ) then return 0, "" end
 
    if ( nRet ~= 0 ) then
        return 2, "getDataObjAttrByKeyAttr 失败!"..id
    end
 
    if ( nRet ~= 0  ) then
        return 2, "getDataObjAttrByKeyAttr 发生错误!"..id
    end
    local strJson
    nRet, strJson = mobox.objAttrsToLuaJson( "Container", strRetInfo )
    if ( nRet ~= 0  ) then
        return 2, "objAttrsToLuaJson Container 失败!"..strRetInfo
    end
 
    local object, success
    success, object = pcall( json.decode, strJson )
    if ( success == false ) then
        return 2,"objAttrsToLuaJson('Container') 返回的的JSON格式不合法!" 
    end
    object.id = id
    return 0, object
end
 
--[[
    判断某个编号的容器是否被作业使用
    参数:
        cntr_code   容器编码
    返回:
        nRet
        strRet -- yes -- 表示容器在作业中  no -- 表示没有
-- ]]
function wms_cntr.InOperation( strLuaDEID, cntr_code )
    local strCondition
    local nRet, strRetInfo
 
    strCondition = "( N_B_STATE = 0 OR N_B_STATE = 1)  AND S_CNTR_CODE = '"..cntr_code.."'"
    nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "Operation", strCondition )
    if ( nRet ~= 0 ) then return nRet, strRetInfo end 
    local nCount = lua.StrToNumber( strRetInfo )   
    if ( nCount == 0 ) then return 0, "no" end
    return 0, "yes"
end
 
--[[
    判断某个编号的容器是否存在
    参数:
        cntr_code   容器编码
    返回:
        true 存在  false 不存在
-- ]]
function wms_cntr.Exist( strLuaDEID, cntr_code )
    local nRet, strRetInfo
    if ( cntr_code == nil or  cntr_code == '' ) then 
        return false
    end
    local strCondition = "S_CODE = '"..cntr_code.."'"
    nRet, strRetInfo = mobox.existThisData( strLuaDEID, "Container", strCondition )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "existThisData 时失败! "..strRetInfo ) end
    if ( strRetInfo == 'no' ) then return false end
    return true
end
 
--[[
    判断某个货位是否有绑定过容器
    参数:
        loc_code   货位编码
    返回:
        true 存在  false 不存在
-- ]]
function wms_cntr.Loc_Container_Exist( strLuaDEID, loc_code )
    local nRet, strRetInfo
    if ( cntr_code == nil or  cntr_code == '' ) then 
        return false
    end
    local strCondition = "S_LOC_CODE = '"..loc_code.."'"
    nRet, strRetInfo = mobox.existThisData( strLuaDEID, "Loc_Container", strCondition )
    if ( nRet ~= 0 ) then lua.Error( strLuaDEID, debug.getinfo(1), "existThisData 时失败! "..strRetInfo ) end
    if ( strRetInfo == 'no' ) then return false end
    return true
end
 
--[[
    获取货位中的容器编码
    参数:
        loc_code   货位编码
    返回:
        nRet  非0 失败
        cntr_set: ["C1","C2"]  这是一个table对象
                  如果没有容器 返回空字符串
-- ]]
function wms_cntr.Get_Loc_Container( strLuaDEID, loc_code )
    local nRet, strRetInfo
 
    if ( loc_code == nil or  loc_code == '' ) then 
        return 1, "wms_cntr.Get_Loc_Container 函数中loc_code不能为空!"
    end
    local strCondition = "S_LOC_CODE = '"..loc_code.."'"
    local strOrder = "N_BIND_ORDER"
    nRet, strRetInfo = mobox.queryDataObjAttr( strLuaDEID, "Loc_Container", strCondition, strOrder, "S_CNTR_CODE" )
    if ( nRet ~= 0 ) then return 1, "获取【Loc_Container】失败! "..strRetInfo end
    if ( strRetInfo == '' ) then return 0, "" end
 
    local retObjs = json.decode( strRetInfo )
    local n
    local attrs
    local cntr_set = {}
 
    for n = 1, #retObjs  do
        attrs = retObjs[n].attrs
        cntr_set[n] = attrs[1].value
    end
 
    return 0, cntr_set
end
 
--[[
    获取容器所在货位
    参数:
        cntr_code   容器编码
    返回:
        nRet  非0 失败
        str_loc_code 货位编码
-- ]]
function wms_cntr.Get_Container_Loc( strLuaDEID, cntr_code )
    local nRet, strRetInfo
 
    if ( cntr_code == nil or  cntr_code == '' ) then 
        return 1, "wms_cntr.Get_Container_Loc 函数中 cntr_code 不能为空!"
    end
    local strCondition = "S_CNTR_CODE = '"..cntr_code.."'"
    local strOrder = "N_BIND_ORDER"
    nRet, strRetInfo = mobox.queryDataObjAttr( strLuaDEID, "Loc_Container", strCondition, strOrder, "S_LOC_CODE" )
    if ( nRet ~= 0 ) then return 1, "获取【Loc_Container】失败! "..strRetInfo end
    if ( strRetInfo == '' ) then return 0, "" end
 
    local retObjs = json.decode( strRetInfo )
    if ( #retObjs > 1 ) then
        return 1, "容器'"..cntr_code.."'在多个货位,数据错误请及时处理!"
    end
 
    local attrs
    attrs = retObjs[1].attrs
    return 0, attrs[1].value
end
 
-- 更新 INV_Detail 会触发变更后事件
function wms_cntr.INV_Detail_Update( strLuaDEID, inv_detail_data )
    if ( inv_detail.id == nil or inv_detail.id == '' ) then
        return 1, "调用 wms_cntr.INV_Detail_Update 函数时参数不正确, ID不能为空!"
    end
    local nRet, strAttrs
    nRet, strAttrs = mobox.objJsonToObjAttr(inv_detail.cls, lua.table2str(inv_detail))
    if ( nRet ~= 0 ) then return nRet, strAttrs end
 
    local strUpdate = '[{"id":"'..inv_detail.id..'","attrs":'..strAttrs..'}]'
    local strRetInfo
 
    nRet, strRetInfo = mobox.updateDataObj( strLuaDEID, inv_detail.cls, strUpdate, 1 )
    if ( nRet ~= 0 ) then
        return nRet, strRetInfo
    end
    return 0, "ok"
end
 
--[[
    容器加解锁
    cntr -- 容器对象/或容器编码
    str_lock_state -- 锁状态名称
    lock_op_no -- 加锁的业务编码
--]]
function wms_cntr.SetLock( strLuaDEID, cntr, str_lock_state, lock_op_no )
    local nRet, strRetInfo
 
    if ( str_lock_state == nil or str_lock_state == '') then
        return 1, "wms_cntr.SetLock 函数中 str_lock_state 不能为空!"
    end
    -- 如果 cntr 是一个字符串那就是容器编码
    if ( type(cntr) == "string" ) then
        nRet, cntr = wms_cntr.GetInfo( strLuaDEID, cntr )
        if ( nRet ~= 0 ) then return 1, "获取【容器】失败! " .. cntr end        
    else
        if ( cntr == nil or  cntr.cls ~= "Container" ) then 
            return 1, "wms_cntr.SetLock 函数中 cntr 不能为空,并且必须是容器对象"
        end
    end
    if ( lock_op_no == nil ) then lock_op_no = '' end
 
    local lock_state
    if ( type(str_lock_state) == "string") then
        lock_state = wms_base.Get_nConst( strLuaDEID, str_lock_state )
    else
        lock_state = str_lock_state
    end
    local str_lock_state = wms_base.GetDictItemName( strLuaDEID, "WMS_LocationLockState", lock_state )
 
    if ( lock_state ~= 0 ) then
        -- 如果已经有锁是不能继续加其它锁的
        if (cntr.lock_state ~= 0) then
            return 1, "容器'"..cntr.code.."'已经被业务'"..cntr.lock_op_code.."'锁定,不能继续加锁!"
        end
    end
 
    -- 更新容器表Lock相关属性
    local strCondition = "S_CODE = '"..cntr.code.."'"
    local strSetAttr = "N_LOCK_STATE = "..lock_state..", S_LOCK_STATE = '"..str_lock_state.."', S_LOCK_OP_CODE ='"..lock_op_no.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container", strCondition, strSetAttr )
    if ( nRet ~= 0 ) then  lua.Error( strLuaDEID, debug.getinfo(1), "更新【容器】锁状态失败!"..strRetInfo ) end   
    return 0, ""  
end
 
--[[ 
    设置 Container_Cell 里的属性
    ctd -- 容器类型定义
    cell_list -- 当前容器中 Container_Cell 列表
    cell_no -- 需要设置的料格编码
    cntr_cell -- 料格属性
    inv_detail_data 是比较全的 INV_Detail 中在本料格的属性,可以从中获取 match_attr 并且保存到 Container_Cell, 后面的 update 会用到
--]]
local function set_cell_list( ctd, cell_list, cell_no, cntr_cell, inv_detail_data )
    local n, nRet
    local volume = 0
    local limit = 0
    local weight = 0
    local sku_count_method = inv_detail_data.S_COUNT_METHOD or ''
    local cell_max_weight = 0
 
    -- 获取料格计数的一些基本属性
    if sku_count_method == "Limit" then
        -- 如果料格是通过数量限制是否满格
        local sku_grid_parm, success
        if not lua.StrIsEmpty( inv_detail_data.S_SKU_GRID_PARM ) then
            success, sku_grid_parm = pcall( json.decode, inv_detail_data.S_SKU_GRID_PARM )
            if ( success == false ) then
                return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 S_SKU_GRID_PARM 不符合json规范!" 
            end  
            nRet, limit = wms_base.Get_LoadingLimit( inv_detail_data.S_ITEM_CODE, sku_grid_parm, ctd.ctd_code, cntr_cell.cell_type )
            if nRet ~= 0 then
                return 1, limit
            end
            if limit <= 0 then
                return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 S_SKU_GRID_PARM 不合规!"
            end            
        elseif not lua.StrIsEmpty( inv_detail_data.N_LOADING_LIMIT ) then
            limit = lua.Get_NumAttrValue( inv_detail_data.N_LOADING_LIMIT )
            if limit <= 0 then
                return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 N_LOADING_LIMIT 不合规!"
            end
        else
            return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 S_SKU_GRID_PARM 为空!" 
        end
    elseif sku_count_method == "Weight" then
        -- 需要获取料格最大载重
        local grid_def
        nRet, grid_def = wms_cntr.Get_CTD_GridDef( ctd, cntr_cell.cell_type )
        if nRet ~= 0 or grid_def.box_num <= 0 then
            return 1, "容器类型'"..ctd.ctd_code.."'中没有定义 cell_type = '"..cntr_cell.cell_type.."'的定义"
        end
        cell_max_weight = ctd.load_capacity/grid_def.box_num
        if cell_max_weight <= 0 then
            return 1, "容器类型'"..ctd.ctd_code.."'中没容器载重定义不合规!"
        end
 
        weight = lua.Get_NumAttrValue( inv_detail_data.F_WEIGHT )
        if weight <= 0 then
            return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 F_WEIGHT 不合规!"
        end        
    elseif sku_count_method == "Volume" or sku_count_method == "Mixed" then
        volume = lua.Get_NumAttrValue( inv_detail_data.F_VOLUME )
        if volume <= 0 then
            return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 F_VOLUME 不合规!"
        end         
    else
        return 1, "SKU 编码 = '"..inv_detail_data.S_ITEM_CODE.."' 的数据对象中 S_COUNT_METHOD 为空或不合规!" 
    end
 
    for n = 1, #cell_list do
        if ( cell_list[n].cell_no == cell_no ) then
            cell_list[n].good_volume = cntr_cell.good_volume
            cell_list[n].good_weight = cntr_cell.good_weight
            cell_list[n].qty = cntr_cell.qty
            cell_list[n].item_code = cntr_cell.item_code
            cell_list[n].item_name = cntr_cell.item_name
            cell_list[n].item_state = cntr_cell.item_state
            cell_list[n].storer = cntr_cell.storer
            cell_list[n].wms_bn = cntr_cell.wms_bn
            cell_list[n].cell_type = cntr_cell.cell_type
            cell_list[n].item_cell_type = cntr_cell.item_cell_type
            cell_list[n].inv_detail_data = inv_detail_data
            if ( cell_list[n].item_cell_type < cell_list[n].cell_type ) then
                cell_list[n].state = "Abnormal"
            else
                cell_list[n].state = "Normal"
            end
 
            -- 如果料格被强制设置为满
            if ( cell_list[n].forced_fill == 'Y' ) then
                cell_list[n].empty_full = 2
            else
                if sku_count_method == "Volume" or sku_count_method == "Mixed" then
                    if ( (cntr_cell.good_volume + volume) > cell_list[n].volume ) then 
                        cell_list[n].empty_full = 2
                    else
                        cell_list[n].empty_full = 1
                    end
                elseif sku_count_method == "Limit" then
                    if cntr_cell.qty >= limit then
                        cell_list[n].empty_full = 2
                    else
                        cell_list[n].empty_full = 1
                    end    
                elseif sku_count_method == "Weight" then   
                    if ( (cntr_cell.good_weight + weight) > cell_max_weight ) then 
                        cell_list[n].empty_full = 2
                    else
                        cell_list[n].empty_full = 1
                    end                                                         
                end
            end
            return 0
        end
    end
    return 1, "在 set_cell_list 时失败,无法匹配到料格列表!"
end
 
-- 更新 Container_Cell 里的属性
local function update_container_cell( strLuaDEID, ctd, cell_list )
    local n, nRet, strRetInfo
    local strCondition, strSetAttr
    local si_match_attrs_count = #ctd.si_match_attrs
    local match_attr_update = ''
    local inv_detail_data = {}
    local cntr_cell_data
 
    for n = 1, #cell_list do
        if ( cell_list[n].qty == 0 ) then
            -- 空料格,属性全部设置为初始值
            cntr_cell_data = m3.AllocObject2( strLuaDEID, "Container_Cell" )
            cntr_cell_data.S_CNTR_CODE = cell_list[n].cntr_code
            cntr_cell_data.S_CELL_NO = cell_list[n].cell_no
 
            nRet, strSetAttr = mobox.objJsonToObjAttr( "Container_Cell", lua.table2str(cntr_cell_data))
            if ( nRet ~= 0 ) then 
                return nRet, strSetAttr 
            end
            nRet, strRetInfo = mobox.setDataObjAttr( strLuaDEID, "Container_Cell", cell_list[n].id, strSetAttr )
            if ( nRet ~= 0  ) then
                return 2, "setDataObjAttr 发生错误!"..strRetInfo
            end            
        else
            match_attr_update = ''
            inv_detail_data = cell_list[n].inv_detail_data
            -- 如果料格匹配还有附加的属性
            if ( si_match_attrs_count > 0 ) then
                for m = 1, si_match_attrs_count do
                    if ( lua.isTableEmpty(inv_detail_data) ) then
                        match_attr_update = match_attr_update..","..ctd.si_match_attrs[m].." = ''"
                    else
                        match_attr_update = match_attr_update..","..ctd.si_match_attrs[m].." = '"..inv_detail_data[ctd.si_match_attrs[m]].."'"
                    end
                end
            end
            strCondition = " S_CELL_NO = '"..cell_list[n].cell_no.."' AND S_CNTR_CODE = '"..cell_list[n].cntr_code.."'"
            strSetAttr = "F_GOOD_WEIGHT = "..cell_list[n].good_weight..", F_GOOD_VOLUME = "..cell_list[n].good_volume..", N_EMPTY_FULL = "..cell_list[n].empty_full..
                        ", S_ITEM_CODE = '"..cell_list[n].item_code.."', S_ITEM_NAME = '"..cell_list[n].item_name.."', F_QTY = "..cell_list[n].qty..
                        ", S_WMS_BN = '"..cell_list[n].wms_bn.."', S_ALLOC_OP_CODE = '', S_CELL_TYPE = '"..cell_list[n].cell_type.."'"..
                        ", S_ITEM_CELL_TYPE = '"..cell_list[n].item_cell_type.."', S_STATE = '"..cell_list[n].state.."'"..
                        ", S_ITEM_STATE = '"..cell_list[n].item_state.."', S_STORER = '"..cell_list[n].storer.."'"..match_attr_update
            nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container_Cell", strCondition, strSetAttr )
            if ( nRet ~= 0 ) then  
                return 2, "更新【容器料格】信息失败!"..strRetInfo 
            end 
        end
    end
 
    return 0
end
 
-- 获取料箱空料格数量
local function get_empty_cell_num( cell_list )
    local n, num
 
    num = 0
    for n = 1, #cell_list do
        if ( cell_list[n].empty_full == 0 ) then
            num = num + 1
        end
    end
    return num
end
 
-- 根据料格里的空满状态判断容器是否可以设置为满状态
local function cntr_is_full( cell_list )
    local n, nCount, full_cell_count
 
    nCount = #cell_list
    full_cell_count = 0
    for n = 1, nCount do
        if ( cell_list[n].forced_fill == 'Y' or cell_list[n].empty_full == 2 ) then
            full_cell_count = full_cell_count + 1
        end
    end
    if ( full_cell_count == nCount ) then return true end
    return false
end
 
-- 根据INV_Detail中的信息信息盘点料格和料箱的空满,货物数量,总体积,重量等全部重新计算一次(巨星料箱库项目首次)
-- 在入库前调用一下这个函数确保【容器】【容器料格】的数据准确
-- cntr 是一个容器对象 ctd -- 容器类型定义
local function cntr_reset_by_inv_detail( strLuaDEID, ctd, cntr )
    local strCondition, nRet
    local data_objs
    local nRet, strRetInfo    
 
    strCondition = "S_CNTR_CODE = '"..cntr.code.."'"
    local strOrder = "" 
    local cntr_loc_pos = ""
 
    nRet, strRetInfo = mobox.queryOneDataObjAttr(strLuaDEID, "Loc_Container", strCondition, strOrder, "S_LOC_CODE" )
    if (nRet ~= 0) then return 2, "获取【货位容器绑定】信息失败! " .. strRetInfo end
    if ( strRetInfo ~= "") then 
        local ret_info = json.decode(strRetInfo)
        cntr_loc_pos = ret_info.attrs[1].value
    end
 
    -- type = 3 是料格容器
    if ( cntr.type == "Cell_Box" ) then
        strCondition = "S_CNTR_CODE = '"..cntr.code.."'"        
        nRet, data_objs = m3.QueryDataObject(strLuaDEID, "Container_Cell", strCondition, "S_CELL_NO" )
        if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objs end
        if ( data_objs == '' ) then return 0  end
        
        local n, nCount, nCellCount
        local cell_attrs
 
        nCellCount = #data_objs
        if ( 0 == nCellCount ) then return 0 end
 
        local cell_list = {}
        local attrs = {}
 
        for n = 1, nCellCount do
            obj_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
            local cell = {
                id = data_objs[n].id,
                --attrs = obj_attrs,
                cntr_code = obj_attrs.S_CNTR_CODE,
                cell_no = obj_attrs.S_CELL_NO,
                cell_type = cntr.spec,
                item_code = '',
                item_name = '',
                item_state = '',
                storer = '',
                item_cell_type = '',
                state = 'Normal',
                wms_bn = '',
                qty = 0,
                forced_fill = obj_attrs.C_FORCED_FILL,
                volume = lua.StrToNumber( obj_attrs.F_VOLUME ),   -- 料格体积
                good_volume = 0,
                good_weight = 0,
                empty_full = 0,
                inv_detail_data = {}
            }
            table.insert( cell_list, cell )
        end
 
        -- 获取CG_Detial + SKU 中的属性
        local strTable = "TN_INV_Detail a LEFT JOIN TN_SKU b ON ( a.S_ITEM_CODE = b.S_ITEM_CODE and a.S_STORER = b.S_STORER )" 
        -- 要查询的属性
        local strAttrs = ""
        local m
        local INV_DETAIL_BASE_ATTRS_COUNT = #INV_DETAIL_BASE_ATTRS
        local attr_set = {}
        for m = 1, INV_DETAIL_BASE_ATTRS_COUNT do
            strAttrs = strAttrs.."a."..INV_DETAIL_BASE_ATTRS[m]..","
            table.insert( attr_set, INV_DETAIL_BASE_ATTRS[m] )
        end
        local UDF_ATTRS_COUNT = #UDF_ATTRS
        for m = 1, UDF_ATTRS_COUNT do
            strAttrs = strAttrs.."a."..UDF_ATTRS[m]..","
            table.insert( attr_set, UDF_ATTRS[m] )
        end
        strAttrs = strAttrs.."b.F_VOLUME, b.F_WEIGHT, b.S_CELL_TYPE, b.S_SKU_GRID_PARM, b.S_COUNT_METHOD, b.N_LOADING_LIMIT"
        table.insert( attr_set, "F_VOLUME" )
        table.insert( attr_set, "F_WEIGHT" )
        table.insert( attr_set, "S_CELL_TYPE" )
        table.insert( attr_set, "S_SKU_GRID_PARM" )
        table.insert( attr_set, "S_COUNT_METHOD" )
        table.insert( attr_set, "N_LOADING_LIMIT" )
 
        -- 入库批次也要进行排序
        strOrder = "a.S_CELL_NO, a.S_WMS_BN"
        strCondition = "a.S_CNTR_CODE = '"..cntr.code.."'"
        nRet, strRetInfo = mobox.queryMultiTable(strLuaDEID, strAttrs, strTable, 2000, strCondition, strOrder )
        if ( nRet ~= 0 ) then return 2, "queryMultiTable 失败!"..strRetInfo end
 
        local n, qty
        local ret_data = {}
        if ( strRetInfo ~= '' ) then     
            ret_data = json.decode(strRetInfo)
            nCount  = #ret_data
        else
            nCount = 0
        end
 
        local cell_no, item_cell_type
        local current_cell_no = ''
        local sum_volume, sum_weight, sum_qty
        local volume, weight, qty
        local item_code, item_name, wms_bn
        local cg_detail_count = nCount
        local good_weigth, good_volume, good_num
        local cell_type = cntr.spec
 
        -- 计算料箱料格的体积、重量,已经料箱格的空满状态
        -- 料格里的货品重量、体积
        sum_volume = 0
        sum_weight = 0
        sum_qty = 0
        -- 整个料箱里的货品重量、体积
        good_weigth = 0
        good_volume = 0
        good_num = 0
 
        local cntr_cell = {}
        local inv_detail_data = {}
        local data_obj
        local mixing_rule_value = {}        -- 混箱属性值 {S_BATCH_NO = 'x'}
 
        if ( nCount > 0 ) then
            for n = 1, nCount do
                nRet, data_obj = lua.GetDataAttrObj_By_StrArray( attr_set, ret_data[n] )
                if ( nRet ~= 0 ) then
                    return 1, data_obj
                end
                cell_no = lua.Get_StrAttrValue( data_obj.S_CELL_NO )  
                if ( current_cell_no == '' ) then
                    current_cell_no = cell_no
                end
                if ( current_cell_no ~= cell_no ) then
                    cntr_cell.qty = sum_qty
                    cntr_cell.good_volume = sum_volume
                    cntr_cell.good_weight = sum_weight
 
                    nRet, strRetInfo = set_cell_list( ctd, cell_list, current_cell_no, cntr_cell, inv_detail_data )
                    if nRet ~= 0 then
                        return 1, strRetInfo
                    end
 
                    sum_volume = 0
                    sum_weight = 0 
                    sum_qty = 0
                    current_cell_no = cell_no           
                end
 
                if ctd.have_mixing_rule then
                    -- 获取混箱属性
                    for _, mixing_attr in ipairs(ctd.mixing_attrs) do
                        mixing_rule_value[mixing_attr] = data_obj[mixing_attr]
                    end
                end
 
                cntr_cell.cell_type = cell_type
                cntr_cell.item_code = lua.Get_StrAttrValue( data_obj.S_ITEM_CODE )
                cntr_cell.item_name = lua.Get_StrAttrValue( data_obj.S_ITEM_NAME )
                cntr_cell.item_state = lua.Get_StrAttrValue( data_obj.S_ITEM_STATE )
                cntr_cell.storer = lua.Get_StrAttrValue( data_obj.S_STORER )
                cntr_cell.wms_bn = lua.Get_StrAttrValue( data_obj.S_WMS_BN )
                inv_detail_data = data_obj
 
                qty = lua.Get_NumAttrValue( data_obj.F_QTY )    
                volume = lua.Get_NumAttrValue( data_obj.F_VOLUME )   
                weight = lua.Get_NumAttrValue( data_obj.F_WEIGHT)   
                cntr_cell.item_cell_type = lua.Get_StrAttrValue( data_obj.S_CELL_TYPE )  
 
                volume = volume*qty
                weight = weight*qty
 
                good_weigth = good_weigth + weight
                good_volume = good_volume + volume
                good_num = good_num + qty
 
                sum_volume = sum_volume + volume
                sum_weight = sum_weight + weight
                sum_qty = sum_qty + qty
            end
            cntr_cell.qty = sum_qty
            cntr_cell.good_volume = sum_volume
            cntr_cell.good_weight = sum_weight
            
            nRet, strRetInfo = set_cell_list( ctd, cell_list, current_cell_no, cntr_cell, inv_detail_data )
            if nRet ~= 0 then
                return 1, strRetInfo
            end
            -- 设置 容器扩展属性
            if ctd.have_mixing_rule then
                nRet, strRetInfo = wms_cntr.Add_CNTR_ExtInfo( strLuaDEID, cntr.code, ctd.mixing_attrs, mixing_rule_value )
                if nRet ~= 0 then
                    return 1, strRetInfo
                end                
            end
        else
            -- 空料箱,混箱属性也需要清空
            if ctd.have_mixing_rule then
                strCondition = "S_CNTR_CODE = '" .. cntr.code .."'"
                nRet, strRetInfo = mobox.dbdeleteData(strLuaDEID, "Container_Ext", strCondition)
                if (nRet ~= 0) then 
                    return 1, "删除【Container_Ext】失败!"..strRetInfo
                end                   
            end
        end
 
        -- 更新【容器料格】属性
        nRet, strRetInfo = update_container_cell( strLuaDEID, ctd, cell_list )
        if ( nRet ~= 0 ) then 
            return 2, strRetInfo 
        end
        
        -- 更新【容器】本身属性
        local empty_cell_num  = get_empty_cell_num( cell_list )
        local strSetAttr
        local empty_full = 0
        
        -- V2.0 MDF BY WHB 对容器空满状态判断的改进
        if ( cntr.forced_fill == 'Y' ) then
            empty_full = 2
        else
            if ( empty_cell_num == cntr.max_cell_num ) then
                empty_full = 0
            else
                empty_full = 1
                if ( 0 == empty_cell_num ) then
                    -- 判断一下容器里的料格是否都是已经满,那么料箱也要设置为满
                    if ( cntr_is_full( cell_list ) ) then
                        empty_full = 2
                    end
                end
            end
        end
        if ( cntr.max_weight > 0 ) then
            if ( ( good_weigth + cntr.weight ) >= cntr.max_weight ) then
                empty_full = 2
            end
        end
 
        strCondition = "S_CODE = '"..cntr.code.."'"
        strSetAttr = "N_ALLOC_CELL_NUM = 0, N_EMPTY_CELL_NUM = "..empty_cell_num..", N_EMPTY_FULL = "..empty_full..", N_DETAIL_COUNT = "..cg_detail_count..
                     ", F_GOOD_WEIGHT = "..good_weigth..", F_GOOD_VOLUME = "..good_volume..", N_GOOD_NUM = "..good_num..", S_POSITION = '"..cntr_loc_pos.."'"
        nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container", strCondition, strSetAttr )
        if ( nRet ~= 0 ) then  return 2, "更新【容器料格】信息失败!"..strRetInfo end 
    else
        -- 重置一下 容器中的 N_DETAIL_COUNT 即可
        strCondition = "S_CNTR_CODE = '"..cntr.code.."'"
        nRet, strRetInfo = mobox.getDataObjCount( strLuaDEID, "INV_Detail", strCondition )
        if ( nRet ~= 0 ) then 
            return 2, strRetInfo 
        end 
        nCount = lua.StrToNumber( strRetInfo )
        strCondition = "S_CODE = '"..cntr.code.."'"       
        strSetAttr = "N_DETAIL_COUNT = "..nCount..", S_POSITION = '"..cntr_loc_pos.."'"
        nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container", strCondition, strSetAttr )
        if ( nRet ~= 0 ) then  
            return 2, "更新【容器料格】信息失败!"..strRetInfo 
        end 
        if nCount == 0 then
            -- 空料箱,混箱属性也需要清空
            if ctd.have_mixing_rule then
                strCondition = "S_CNTR_CODE = '" .. cntr.code .."'"
                nRet, strRetInfo = mobox.dbdeleteData(strLuaDEID, "Container_Ext", strCondition)
                if (nRet ~= 0) then 
                    return 1, "删除【Container_Ext】失败!"..strRetInfo
                end                   
            end            
        else
            strCondition = "S_CNTR_CODE = '"..cntr.code.."'"
            nRet, data_obj = m3.GetDataObjByCondition2( strLuaDEID, "INV_Detail", strCondition, "T_CREATE" )
            if ( nRet == 0  ) then
                nRet, strRetInfo = wms_cntr.Add_CNTR_ExtInfo( strLuaDEID, cntr.code, ctd.mixing_attrs, data_obj )
                if nRet ~= 0 then
                    return 1, strRetInfo
                end                                 
            end
        end
    end
 
    return 0
end
 
function wms_cntr.Reset( strLuaDEID, cntr )
    local strCondition, nRet, strRetInfo
 
    if ( cntr == nil ) then
        return 1, "Rest函数中cntr必须有值!"
    end
    local ctd       -- 容器类型定义
    nRet, ctd = wms_cntr.GetCTDInfo( cntr.ctd_code )
    if ( nRet ~= 0 ) then
        return 2, ctd
    end    
 
    -- 重置容器属性
    nRet, strRetInfo = cntr_reset_by_inv_detail( strLuaDEID, ctd, cntr )
    if ( nRet ~= 0 ) then
        return 2, strRetInfo
    end          
    return 0 
end
 
function wms_cntr.Lock( strLuaDEID, cntr_code, lock_state, op_code )
    local nRet, strRetInfo
 
    nRet, strRetInfo = wms.wms_LockCntr( cntr_code, 2, op_code )
    if ( nRet ~= 0 ) then
        return 1, "给容器'"..cntr_code.."'加出库锁失败!"  
    end 
 
    local strCondition = "S_CODE = '"..cntr_code.."'"
    local strSetAttr = "N_LOCK_STATE = "..lock_state..", S_LOCK_OP_CODE = '"..op_code.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition(strLuaDEID, "Container", strCondition, strSetAttr )
    if (nRet ~= 0) then return 2, "设置【Container】状态失败!"..strRetInfo end
 
    return 0
end
 
-- 容器中的 N_ALLOC_CELL_NUM + 1, 料格 N_EMPTY_FULL = 3 (预分配)
-- bs_no 产生预分配料格的业务编码
-- 预分配一个容器料格
function wms_cntr.CNTR_cell_alloc_set( strLuaDEID, cntr_code, cell_no, bs_no )
    local nRet, strRetInfo
    local strCondition = "S_CODE = '"..cntr_code.."'"
    local strUpdateSql = "N_ALLOC_CELL_NUM = N_ALLOC_CELL_NUM + 1"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container", strCondition, strUpdateSql )
    if ( nRet ~= 0 ) then  return 1, strRetInfo end
 
    strCondition = "S_CELL_NO = '"..cell_no.."' AND S_CNTR_CODE = '"..cntr_code.."'"
    strUpdateSql = "N_EMPTY_FULL = 3, S_ALLOC_OP_CODE = '"..bs_no.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Container_Cell", strCondition, strUpdateSql )
    if ( nRet ~= 0 ) then 
        return 1, strRetInfo
    end
    return 0
end
 
--[[
  检查容器是否可以用于作业中,如果容器在一个非完成的作业中,不能进行用这个容器创建作业
  返回值:
  nRet, can_usedinop
--]]
function wms_cntr.CanUsedInOperation( strLuaDEID, cntr_code )
    local nRet, strRetInfo
 
    if ( cntr_code == '' or cntr_code == nil ) then
        return 1, "CanUsedInOperation 函数中参数 cntr_code 必须有值!"
    end
 
    local strCondition = "N_B_STATE IN ( 0,1,3,4,5,6 ) AND S_CNTR_CODE = '"..cntr_code.."'"
    nRet, strRetInfo = mobox.existThisData( strLuaDEID, "Operation", strCondition )
    if ( nRet ~= 0 ) then return 2, strRetInfo  end
    if ( strRetInfo == "yes" ) then
        -- 检测容器已经存在没完成的作业 
        return 0, false 
    end
    return 0, true
end
 
--  容器定义相关 ---
--[[ 
    检查混箱,匹配等属性是否正确,并且返回这些属性的类型值
    输入参数: str_attrs -- S_BATCH_NO,S_UDF01,... 属性字符串
    返回: nRet, attrs, attrs_def_list
    attrs -- {"S_BATCH_NO","S_UDF01",...}
    attrs_def_list = { { attr = "S_ITEM_CODE", type = "string/number"},...}
--]]
local function check_attrs( str_attrs )
    local nRet, strRetInfo
    local attr_type
    local attrs_def_list = {}
    local attrs = {}
    local attr_type_def = {}
    
    if str_attrs == nil or str_attrs == '' then
        return 0, {}
    end
    local seg = lua.split( str_attrs, ";" )    
    for i = 1, #seg do
        table.insert( attrs, seg[i] )
    end
    -- 获取匹配属性的类型(数据类为 INV_Detail)
 
    nRet, strRetInfo = mobox.getClassAttrType( "INV_Detail", lua.table2str( attrs ) )   
    if nRet ~= 0 then
        return 1, strRetInfo
    end     
    attr_type_def = json.decode( strRetInfo )
 
    -- 上面的这个函数不正确,用下面的暂时替换一下
    for _, attr in ipairs( attrs ) do
        attr_type_def[attr] = "char"
    end
    
    local val
    for _, attr in ipairs( attrs ) do
        attr_type = attr_type_def[attr] or ''
        val = ''
        if attr_type == '' then
            return 1, "属性'"..attr.."'不是 INV_Detial 中定义的属性!"
        elseif attr_type == 'int' or attr_type == 'float' or attr_type == 'bigint' or attr_type == 'dict-int' or attr_type == 'computed' then
            val = 'number'
        else
            val = 'string'
        end
        local attr_type = {
            attr = attr, type = val
        } 
        table.insert( attrs_def_list, attr_type ) 
    end
    return 0, attrs, attrs_def_list
end
 
local function grid_box_num_sort( a, b )
    return a.box_num < b.box_num
end
--[[
        获取容器类型定义数据对象
        有些数据需要进一步细化,比如匹配规则中的属性的类型
-- ]]
function wms_cntr.GetCTDInfo( ctd_code )
    local nRet, strRetInfo
 
    if ( ctd_code == nil or ctd_code == '' ) then
        return 1, "GetCTDInfo 函数中 cntr_code 不能为空!"
    end
    local ctd_data
    nRet, ctd_data = m3.GetDataFromCache( "Container_Type_Def", ctd_code )
    if ( nRet ~= 0 ) then
        return 2, ctd_data
    end
 
    nRet, strRetInfo = mobox.objJsonToLuaJson( "Container_Type_Def", lua.table2str(ctd_data) )
    if ( nRet ~= 0 ) then
        return 1, strRetInfo
    end
    local ctd, success
    success, ctd = pcall( json.decode, strRetInfo )
    if ( success == false ) then
        return 1,"objAttrsToLuaJson('Container') 返回的的JSON格式不合法! --> "..strRetInfo 
    end
    
    -- 混箱规则,如果 mixing_attrs_def 有值满足这些要求的SKU才能放一个料箱
    local str_val = ctd_data.S_MIXING_RULE or ''
    ctd.mixing_attrs_def = {}
    ctd.mixing_attrs = {}
    ctd.have_mixing_rule = false
    if ( str_val ~= '' ) then
        nRet, ctd.mixing_attrs, ctd.mixing_attrs_def = check_attrs( str_val ) 
        if nRet ~= 0 then
            return 1, ctd.mixing_attrs
        end 
        if #ctd.mixing_attrs_def > 0 then
            ctd.have_mixing_rule = true    
        end        
    end
 
    -- 料格里符合下面条件的SKU可以加入这个料格
    ctd.si_enable = ctd.si_enable == 'Y' and true or false
    ctd.si_match_attrs_def = {}
    ctd.si_match_attrs = {}
    local str_val = ctd_data.S_SI_MATCH_ATTRS or ''
    if ( str_val ~= '' ) then
        nRet, ctd.si_match_attrs, ctd.si_match_attrs_def = check_attrs( str_val ) 
        if nRet ~= 0 then
            return 1, ctd.si_match_attrs
        end         
    end
 
    -- 料格里符合下面条件的SKU数量可以相加
    ctd.qty_merge = ctd.qty_merge == 'Y' and true or false  
    ctd.merge_attrs_def = {}
    ctd.merge_attrs = {}
    str_val = ctd_data.S_MERGE_ATTRS or ''
    if ( str_val ~= '' ) then
        nRet, ctd.merge_attrs, ctd.merge_attrs_def = check_attrs( str_val ) 
        if nRet ~= 0 then
            return 1, ctd.merge_attrs
        end          
    end    
 
    ctd.check_capacity = ctd.check_capacity == 'Y' and true or false 
    str_val = ctd_data.S_GRID_BOX_DEF or ''
    if ( str_val ~= '' ) then
        local success
        success, ctd.grid_box_def = pcall( json.decode, str_val )
        if ( success == false ) then
            return 1, "容器类型定义'"..ctd.ctd_code.."'中的料格定义不合规!"
        end
        -- 检查输入的 box_num 不能为 <=0
        for _, box_def in ipairs( ctd.grid_box_def ) do
            if box_def.box_num <= 0 then
                return 1, "容器类型定义'"..ctd.ctd_code.."'中的料格定义不合规! box_num 必须大于0"
            end
            box_def.is_smallest = false  -- 是这种类型料箱中最小的料格
        end    
        -- grid_box_def 根据料格数量进行排序
        table.sort( ctd.grid_box_def, grid_box_num_sort )
        ctd.grid_box_def[#ctd.grid_box_def].is_smallest = true
    else
        ctd.grid_box_def = {}
    end
    return 0, ctd
end
 
-- 根据 cell_type 获取 grid 定义
function wms_cntr.Get_CTD_GridDef( ctd, cell_type )
    if ctd == nil or cell_type == nil then
        return 1, "wms_cntr.Get_CTD_GridDef 函数输入参数不能为 nil"
    end
    for _, box_def_item in ipairs( ctd.grid_box_def) do
        if box_def_item.cell_type == cell_type then
            return 0, box_def_item
        end
    end
    return 1, "在容器类型定义中无法获取料格类型 = '"..cell_type.."' 的料格定义"
end
 
-- 根据 cell_type 获取下一个等级的料格 grid 定义
function wms_cntr.Get_CTD_Next_GridDef( ctd, cell_type )
    if ctd == nil or cell_type == nil then
        return 1, "wms_cntr.Get_CTD_Next_GridDef 函数输入参数不能为 nil"
    end
    for n, box_def_item in ipairs( ctd.grid_box_def) do
        if box_def_item.cell_type == cell_type then
            if n == #ctd.grid_box_def then
                return 0, nil
            end
            return 0, ctd.grid_box_def[n+1]
        end
    end
    return 1, "在容器类型定义中无法获取料格类型 = '"..cell_type.."' 的料格定义"
end
 
--[[ 
    计算料格(如果 container_cell料格里已经有货品,用于补料)能放多少个货品, 根据SKU的 S_COUNT_METHODE 进行可进入料格货品数量
    ctd -- 容器类型定义
    cntr_good_weight -- 容器总重量
    container_cell   -- 容器料格对象
                        {"qty","cntr_code","cell_no","cell_type","good_volume","good_weight" }
    sku -- SKU 物料/货品
            { S_ITEM_CODE = "", ... 
              S_COUNT_METHOD -- None  -- 无(报错)
                                Weight -- 根据重量计算
                                Volume -- 根据体积计算
                                Limit  -- 根据数量限制
                                Mixed  -- 根据料箱剩余重量计算可存储货品数量Qw, 根据料箱格剩余体积计算可存储货品数量Qv 取Qw,Qv最小值作为数量返回
--]]
function wms_cntr.Get_CntrCell_Goods_Qty( ctd, cntr_good_weight, container_cell, sku )
    local sku_volume, sku_weight, sku_count_method
    local sku_loading_limit      -- SKU 在料格里最多可装载数量
    local cntr_max_weight
 
    -- 输入参数判断    
    sku_volume = lua.Get_NumAttrValue( sku.F_VOLUME )
    sku_weight = lua.Get_NumAttrValue( sku.F_WEIGHT )
    sku_count_method = lua.Get_StrAttrValue( sku.S_COUNT_METHOD )
    cntr_max_weight = lua.Get_NumAttrValue( ctd.load_capacity )
    sku_loading_limit = lua.Get_NumAttrValue( sku.N_LOADING_LIMIT )
 
    if ( sku_count_method == '' or sku_count_method == "None" ) then
        return 1, "SKU 编码'"..sku.S_ITEM_CODE.."'的 S_COUNT_METHOD 必须有值且不能为 None!"
    end
 
    if (container_cell.cell_type == '' or container_cell.cell_type == nil ) then
        return 1, "calculate_quantity 参数中cell_type必须有值!"
    end
    
    local n
    local cell_max_volume = 0
    local cell_max_weigt = 0
    local cell_goods_qty = lua.Get_NumAttrValue( container_cell.qty )     -- 料格中已经存在的货品数量
    
    for n = 1, #ctd.grid_box_def do
        if ( ctd.grid_box_def[n].cell_type == container_cell.cell_type ) then
            cell_max_volume = lua.Get_NumAttrValue( ctd.grid_box_def[n].volume )
            cell_max_weigt  = lua.Get_NumAttrValue( ctd.grid_box_def[n].weight )
            if cell_max_weigt <= 0 then
                -- 如果没定义料格载重就用容器总载重除料格数量
                if ctd.grid_box_def[n].box_num > 0 then
                    cell_max_weigt = cntr_max_weight/ctd.grid_box_def[n].box_num
                end
            end
            break
        end
    end    
 
    if ( ctd.check_capacity or sku_count_method == "Weight" or sku_count_method == "Mixed" ) then  
        -- SKU 必须有重量属性
        if (sku_weight <= 0) then
            return 1, "Calculate_Quantity 参数中 SKU 中 F_WEIGHT 必须有值并且是数值类型大于0!"
        end 
        if ( cell_max_weigt <= 0 ) then 
          return 1, "料格类型'"..container_cell.cell_type.."'没有定义料格承重!" 
        end
    end
    if ( sku_count_method == "Volume" or sku_count_method == "Mixed" ) then
        -- SKU 必须有体积属性
        if (sku_volume <= 0) then
            return 1, "Calculate_Quantity 参数中 SKU 中 F_VOLUME 必须有值并且是数值类型大于0!"
        end 
        if ( cell_max_volume <= 0 ) then 
            return 1, "料格类型'"..container_cell.cell_type.."'没有定义体积!" 
        end
    end
    if ( sku_count_method == "Limit" ) then
        -- SKU 必须有 N_LOADING_LIMIT/容器装载上限 属性
        if not lua.isTableEmpty( sku.sku_grid_parm ) then
            nRet, sku_loading_limit = wms_base.GetSKU_LoadingLimit( sku, ctd.ctd_code, container_cell.cell_type )
            if nRet ~= 0  then
                return 1, sku_loading_limit
            end          
        else
            if (sku_loading_limit <= 0) then
                return 1, "Calculate_Quantity 参数中 SKU 中 N_LOADING_LIMIT 必须有值并且是数值类型大于0!"
            end 
        end
    end    
    
    if ( cell_goods_qty < 0 ) then return 1, "容器'"..container_cell.cntr_code.."' 料格  '"..container_cell.cell_no.."' 的货品数量为负数!" end
 
    -- V2.0 加 WMS_Volumetric_Ratio/容积率 MDF BY HAN 20250315   
    local v_ratio
    nRet, v_ratio = wms_base.Get_nConst2( "WMS_Volumetric_Ratio" )   
    if nRet ~= 0 then
        v_ratio = 1
    else
        if ( v_ratio == 0 or v_ratio > 1 ) then v_ratio = 1 end
    end
    cell_max_volume = cell_max_volume * v_ratio
 
    local Qv, Qw, Ql
    if ( sku_count_method == "Mixed" ) then
        -- 根据料箱格剩余体积计算可存储货品数量Qv
        Qv = math.floor(( cell_max_volume - container_cell.good_volume )/sku_volume)  
        if ( Qv < 0 ) then
            return  1, "calculate_quantity 通过体积计算数量失败! cell_max_volume = "..cell_max_volume.." container_cell_good_volume = "..container_cell.good_volume..
                       " 料箱号 = '"..container_cell.cntr_code.."' 料格号 = "..container_cell.cell_no
        end  
 
        -- 根据料箱剩余重量计算可存储货品数量Qw
        if ( ctd.check_capacity ) then
            Qw = math.floor(( cntr_max_weight - cntr_good_weight )/sku_weight)
            if ( Qw < 0 ) then
                return 1, "calculate_quantity 通过重量计算数量失败! cntr_max_weight = "..cntr_max_weight.." cntr_good_weight = "..cntr_good_weight..
                            " 料箱号 = '"..container_cell.cntr_code.."' 料格号 = "..container_cell.cell_no
            end
        else
            return 0, Qv
        end       
        if ( Qw > Qv ) then return 0, Qv end
        return 0, Qw
    elseif ( sku_count_method == "Weight" ) then
        Qw = math.floor(( cell_max_weigt - container_cell.good_weight )/sku_weight)
        if ( Qw < 0 ) then
            return 1, "calculate_quantity 通过重量计算数量失败! cntr_max_weight = "..cntr_max_weight.." cntr_good_weight = "..cntr_good_weight..
                      " 料箱号 = '"..container_cell.cntr_code.."' 料格号 = "..container_cell.cell_no
        end 
        return 0, Qw       
    elseif ( sku_count_method == "Volume" ) then
        Qv = math.floor(( cell_max_volume - container_cell.good_volume )/sku_volume)  
        if ( Qv < 0 ) then
            return 1, "calculate_quantity 通过体积计算数量失败! cell_max_volume = "..cell_max_volume.." container_cell_good_volume = "..container_cell.good_volume..
                      " 料箱号 = '"..container_cell.cntr_code.."' 料格号 = "..container_cell.cell_no
        end 
        return 0, Qv        
    elseif ( sku_count_method == "Limit" ) then
       Ql = sku_loading_limit - cell_goods_qty
       return 0, Ql
    end
    return 1, "计数方法'"..sku_count_method.."'目前没支持"
end
 
--[[
    容器和货位解绑, 并且释放库存量,这个一般用于实施辅助
    输入参数:
        cntr_code --  容器编码
        action_src -- 解绑动作来源
--]]
function wms_cntr.Unbinding( strLuaDEID, cntr_code, action_src )
    local nRet, strRetInfo, strErr
 
    if ( cntr_code == nil or cntr_code == '' ) then
        return 1, "wms_cntr.Unbinding 输入参数 cntr_code 必须有值!"
    end
    if ( action_src == nil ) then action_src = '' end
 
    -- 获取容器是否有绑定货位
    nRet, loc_code = wms_cntr.Get_Container_Loc( strLuaDEID, cntr_code )
    if ( nRet ~= 0 ) then
        return 2, "获取容器位置失败!"..loc_code
    end
    if ( loc_code  == '' ) then return 0 end
 
    -- 把解绑方式,解绑来源加到全局变量中,在 Loc_Container 删除后事件上会用到
    local global_attrs = {
        { attr = "N_BINDING_METHOD",value = BINDING_METHOD.Manual },
        { attr = "S_ACTION_SRC", value = action_src }
    }
    mobox.setGlobalAttr( strLuaDEID, lua.table2str(global_attrs) )
 
    local strCondition = "S_LOC_CODE = '"..loc_code.."' AND S_CNTR_CODE = '"..cntr_code.."'"
    -- 删除数据对象【Loc_Container】会触发该数据类的删除后事件,事件会调用函数 wms_ContainerLocAction
    nRet, strRetInfo = mobox.deleteDataObject( strLuaDEID, "Loc_Container", strCondition )
    if ( nRet ~= 0) then
        return nRet, "删除【货位容器绑定】失败!  "..strRetInfo
    end
    nRet, strRetInfo = wms_inv.After_CntrLoc_UnBinding( strLuaDEID, cntr_code, action_src )
    if ( nRet ~= 0) then
        return nRet, "wms_inv.After_CntrLoc_UnBinding 失败!  "..strRetInfo
    end    
    return 0 
end
 
--[[
    获取容器+容器扩展属性,返回 数据对象字段属性 对象
    输入参数:
        cntr_code --  容器编码
--]]
function wms_cntr.Get_Container_ExtInfo( strLuaDEID, cntr_code )
    local nRet, strRetInfo
 
    local strTable = "TN_Container a LEFT JOIN TN_Container_Ext b ON a.S_CODE = b.S_CNTR_CODE" 
    local strCondition =  "a.S_CODE = '"..cntr_code.."'"
 
    -- 组织查询属性
    local attr_set = {}
    local strAttrs = ''
    local UDF_ATTRS_COUNT = #UDF_ATTRS
    local CNTR_BASE_ATTRS_COUNT = #CNTR_BASE_ATTRS
    local CNTR_EXT_BASE_ATTRS_COUNT = #CNTR_EXT_BASE_ATTRS
 
    for m = 1, CNTR_BASE_ATTRS_COUNT do
        strAttrs = strAttrs.."a."..CNTR_BASE_ATTRS[m]..","
        table.insert( attr_set, CNTR_BASE_ATTRS[m] )
    end
    for m = 1, CNTR_EXT_BASE_ATTRS_COUNT do
        strAttrs = strAttrs.."b."..CNTR_EXT_BASE_ATTRS[m]..","
        table.insert( attr_set, CNTR_EXT_BASE_ATTRS[m] )
    end
    for m = 1, UDF_ATTRS_COUNT do
        strAttrs = strAttrs.."b."..UDF_ATTRS[m]..","
        table.insert( attr_set, UDF_ATTRS[m] )
    end
 
    nRet, strRetInfo = mobox.queryMultiTable( strLuaDEID, lua.trim_laster_char( strAttrs ), strTable, 1, strCondition, "" )
    if (nRet ~= 0) then 
        return 2,"queryMultiTable 失败!"..strRetInfo
    end
    if ( strRetInfo == '' ) then 
        return 1, "容器'"..cntr_code.."不存在!"  
    end
    local ret_attr = json.decode(strRetInfo)
    local cntr_ext = {}
    local n = 1
 
    -- 获取联表查询属性
    for m = 1, CNTR_BASE_ATTRS_COUNT do
        cntr_ext[CNTR_BASE_ATTRS[m]] = ret_attr[1][n]
        n = n + 1
    end
    for m = 1, CNTR_EXT_BASE_ATTRS_COUNT do
        cntr_ext[CNTR_EXT_BASE_ATTRS[m]] = ret_attr[1][n]
        n = n + 1
    end
    for m = 1, UDF_ATTRS_COUNT do
        cntr_ext[UDF_ATTRS[m]] = ret_attr[1][n]
        n = n + 1
    end  
    return 0, cntr_ext
 
end
 
return wms_cntr