lzh
2025-06-19 3a6436e0c88042c6ce8dca2fe8adb0109f0ad9e4
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
--[[
    版本:     Version 1.0
    创建日期: 2025-1-26
    创建人:   HAN
 
    功能:
        和出库相关的操作,如配盘
 
        -- Shipping_Order_CheckState                    检查【发货单明细】 配盘是否全部完成,如果全部完成 N_B_STATE = 1
        ** Creat_Distribution                       创建配盘及配盘明细(适合手工配盘)
        -- Check_Distribution_WholeOut              检测配盘是否为整出
        -- Get_Outbound_Detail_list                 根据输入的出库单编码获取 出库单明细列表,oo_no_set 是一个出库单编码数组,如["OO010","OO012"]
        ** Split_Distribution_CNTR_Detail           波次合并后产生的 配盘明细 批分到每个出库单明细
        ** Distribution_Procedure                   根据货品列表系统自动生成配盘和配盘明细,条件是根据货品编码 item_code 确定货品,
                                                        根据批次号顺序配盘,批次号小的优先出
        -- Creat_Distribution_list                  创建配盘及配盘明细数据对象
        -- Sum_outbound_detail                      把出库单明细中的 item 加入出库明细汇总链表
        -- Distribution_CNTR_Detail_PostProcess     配盘明细的后处理程序(分拣出库后对数据的处理)
    ** 标记的是主要函数
 
    更改说明:
        V2.0 HAN 20241122 Distribution_Procedure 增加全属性覆盖,条件判断改进
--]]
 
wms_cntr = require ("wms_container")
wms_wh   = require ("wms_wh")
 
local wms_out = {_version = "0.1.1"}
 
-- 检查 发货单明细 配盘是否全部完成,如果全部完成 N_B_STATE = 1
function wms_out.Shipping_Order_CheckState ( strLuaDEID, shipping_no ) 
    local n, nRet, strRetInfo
 
    -- 获取【发货单明细】
    local strCondition = "S_SHIPPING_NO = '"..shipping_no.."'"
    local strOrder = ""
    local data_objs
    local distribution_finish = true
 
    nRet, data_objs = m3.QueryDataObject( strLuaDEID, "Shipping_Detail", strCondition, strOrder )
    if (nRet ~= 0) then return 1, "获取【Shipping_Detail】信息失败! " .. data_objs end
    local obj_attrs
    local qty, acc_d_qty
 
    for n = 1, #data_objs do
        obj_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
        qty = lua.StrToNumber( obj_attrs.F_QTY )
        acc_d_qty = lua.StrToNumber( obj_attrs.F_ACC_D_QTY )
        if  ( qty > acc_d_qty ) then
            distribution_finish = false
            break
        end
    end
    if ( distribution_finish ) then
        local strUpdateSql = "N_B_STATE = 1"
        strCondition = "S_NO = '"..shipping_no.."'"
        nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Shipping_Order", strCondition, strUpdateSql )
        if ( nRet ~= 0 ) then  return 1, "更新【发货单】信息失败!"..strRetInfo end 
    end
    return 0
end 
 
-- 检测配盘是否为整出, 判断的依据是 CG_Detail中的 qty - 配盘明细中的qty 后的值全部为 0
-- distribution_id 【配盘】对象标识
-- nRet = 0 程序执行无错误 否则 > 0. strRetInfo = "yes" 表示是整拖出
function wms_out.Check_Distribution_WholeOut( strLuaDEID, distribution_cntr )
    local nRet, strRetInfo, n, i
    
    if ( distribution_id == nil or type(distribution_cntr) ~= 'table' ) then
        return 1, "wms_out.Check_Distribution_WholeOut 函数中 distribution 不能为空,必须为table!"
    end
 
    -- 获取【配盘明细】
    local data_objects
    local strCondition = "S_DC_NO = '"..distribution_cntr.dc_no.."'"
    nRet, data_objects = m3.QueryDataObject(strLuaDEID, "Distribution_CNTR_Detail", strCondition, "S_ITEM_CODE" )
    if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objects end
    if ( data_objects == '') then return 0, "no" end
    local dc_detail = {}
    for n = 1, #data_objects do
        dc_detail[n] = m3.KeyValueAttrsToObjAttr(data_objects[n].attrs)
    end
 
    -- 获取【CG_Detail】
    local strCondition = "S_CNTR_CODE = '"..distribution_cntr.cntr_code.."'"
    nRet, data_objects = m3.QueryDataObject(strLuaDEID, "CG_Detail", strCondition, "S_ITEM_CODE" )
    if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objects end
    if ( data_objects == '') then return 0, "no" end
    local obj_attrs
 
    if ( #data_objects > #dc_detail ) then 
        -- 如果容器中的货品条数 > 配盘明细 肯定不是整托出
        return 0, "no"
    end
 
    -- 遍历 容器货品明细 判断:cg_detail.qty - dc_detail.qty = 0
    local cg_detail_qty, dc_detail_qty
    local count = #dc_detail
    for n = 1, #data_objects do
        obj_attrs = m3.KeyValueAttrsToObjAttr(data_objects[n].attrs)
        cg_detail_qty = lua.StrToNumber( obj_attrs.F_QTY )
        -- 从【Distribution_CNTR_Detail】找到 cg_detial.S_ID 相同的 配盘明细
        for i = 1, count do
            if (dc_detail[i].G_CG_DETAIL_ID == data_objects[n].id) then
                dc_detail_qty = lua.StrToNumber( dc_detail[i].F_QTY )
                if ( cg_detail_qty > dc_detail_qty ) then
                    -- 如果容器货品中的数量大于本次配盘出库数量,说明出不完,不是整托出
                    return 0, "no"
                end
                break
            end
        end
    end
    return 0, "yes"
end
 
--[[ 
    创建配盘及配盘明细, 用于手工配盘
    输入参数 distribution_info  -- 配盘信息,是一个table,格式如下
    {
        bs_type = "Shipping_Ordre",         -- 来源类型(发货单/出库单)
        bs_no = "shipping_no",              -- 来源单号
        bs_row_no = bs_row_no,              -- 来源单行号        
        cntr_code = "",                     -- 容器号
        loc_code = "",                      -- 容器来源货位信息
        cg_detail_id = "xxxx",              -- CG_Detail 中记录标识 S_ID
        item_code = "",                     -- 物料编码
        batch_no = "",                      -- 批次号
        serial_no = "",                     -- 系列号
        qty = 1,                            -- 配盘数量
        loc_code = loc_code,                -- 容器所在货位
        exit_area_code = area_code,         -- 出库口库区编码
        exit_loc_code = ""        
        urgent_outbound = false             -- 是否紧急出库
    }
--]]
-- 返回值 nRet = 0 表示成功配盘  1 -- 表示业务流程不许可 2 -- 程序错误
function wms_out.Creat_Distribution( strLuaDEID, distribution_info )
    local nRet, strRetInfo, strErr
 
    -- step1 判断一下是否已经存在配盘
    -- 查找 容器编码 = cntr_code 并且 状态=0,1,2 的配盘容器
    local distribution_cntr
 
    -- 输入参数检查,一些关键的属性必须有值
    if ( distribution_info.cntr_code == nil or distribution_info.cntr_code == '') then
        return 1,  "输入的配盘信息中必须有 cntr_code !"
    end
    if (  distribution_info.bs_type == nil or  distribution_info.bs_type == '' ) then
        return 1,  "输入的配盘信息中必须有 bs_type !"
    end
    if (  distribution_info.bs_no == nil or  distribution_info.bs_no == '' ) then
        return 1,  "输入的配盘信息中必须有 bs_no !"
    end
    if (  distribution_info.cg_detail_id == nil or  distribution_info.cg_detail_id == '' ) then
        return 1,  "输入的配盘信息中必须有 cg_detail_id !"
    end
    if (  distribution_info.loc_code == nil or  distribution_info.loc_code == '' ) then
        return 1,  "输入的配盘信息中必须有 loc_code !"
    end
    local cntr_code = distribution_info.cntr_code
    local loc
    nRet, loc = wms_wh.GetLocInfo( distribution_info.loc_code )
    if ( nRet ~= 0 ) then return 1, '获取货位信息失败! '..distribution_info.loc_code end   
 
    local strCondition = "S_CNTR_CODE = '"..distribution_info.cntr_code.."' AND N_B_STATE >= 0 AND N_B_STATE <= 3"
    nRet, distribution_cntr = m3.GetDataObjByCondition( strLuaDEID, "Distribution_CNTR", strCondition )
    -- nRet = 0 表示配盘已经存在
    local b_have_sanme_cg_detail = false
    local distribution_detail = {}
    if (nRet == 0) then   
        if ( distribution_cntr.b_state ~= 0 ) then
            return 1, "容器号 = '"..distribution_info.cntr_code.."' 的容器已经配盘完成不能继续配盘!"
        end
 
        -- 进一步判断配盘明细里是否已经存在 该 CG_Detail ID相同的配货信息(发货单一样),如果有数量累计即可,不需要另外创建
        strCondition = "S_DC_NO = '"..distribution_cntr.dc_no.."' AND G_CG_DETAIL_ID = '"..distribution_info.cg_detail_id.."'"
        strCondition = strCondition.." AND S_BS_TYPE = '"..distribution_info.bs_type.."' AND S_BS_NO = '"..distribution_info.bs_no.."'"
        nRet, distribution_detail = m3.GetDataObjByCondition( strLuaDEID,"Distribution_CNTR_Detail", strCondition )
        if (nRet == 0) then    
            b_have_sanme_cg_detail = true
        elseif ( nRet > 1 ) then
            return 1, "在检查[配盘明细]是否在时失败! " .. distribution_detail
        end
        
        -- 如果有紧急配盘,需要把配盘业务状态设置为 = 1(配盘完成)
        if ( distribution_info.urgent_outbound ) then
            -- 检查一下是否为整出
            nRet, strRetInfo = wms_out.Check_Distribution_WholeOut( strLuaDEID, distribution_cntr )
            if ( nRet ~= 0 ) then return nRet, strRetInfo end
            
            strCondition = "S_ID = '"..distribution_cntr.id.."'"
            local strSetAttr = "N_B_STATE = 1, S_B_STATE = '配盘完成' "
            if ( strRetInfo == "yes" ) then
                -- 说明是整个容器的货品都出库
                strSetAttr = strSetAttr..", C_WHOLE_OUT = 'Y'"
            end
            nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Distribution_CNTR", strCondition, strSetAttr )
            if ( nRet ~= 0 ) then return 1, "updateDataAttrByCondition(Distribution_CNTR)失败"..strRetInfo end   
        end        
 
    elseif ( nRet == 1 ) then
        -- step2 如果配盘不存在 创建配盘
        distribution_cntr = m3.AllocObject(strLuaDEID, "Distribution_CNTR")
        distribution_cntr.cntr_code = cntr_code
        distribution_cntr.wh_code = loc.wh_code
        distribution_cntr.area_code = loc.area_code
        distribution_cntr.loc_code = loc.code
        distribution_cntr.exit_area_code = distribution_info.exit_area_code
        distribution_cntr.exit_loc_code = distribution_info.exit_loc_code
 
        -- 如果有紧急配盘,需要把配盘业务状态设置为 = 1(配盘完成)
        if ( distribution_info.urgent_outbound ) then
            -- 检查一下是否为整出
            nRet, strRetInfo = wms_out.Check_Distribution_WholeOut( strLuaDEID, distribution_cntr )
            if ( nRet ~= 0 ) then return nRet, strRetInfo end
 
            distribution_cntr.b_state = 1
            distribution_cntr.b_state_name = "配盘完成"  -- 这地方要改成从字典获取
            if ( strRetInfo == "yes" ) then
                distribution_cntr.whole_out = 'Y'
            end
        end
 
        nRet, distribution_cntr = m3.CreateDataObj( strLuaDEID, distribution_cntr )
        if ( nRet ~= 0 ) then return 1, '创建【配盘】对象失败!'..distribution_cntr end
     
    else
        return 1, "GetDataObjByCondition失败! "..distribution_cntr 
    end    
 
    local dc_no = distribution_cntr.dc_no       -- 获取配盘号
 
    -- step3 创建【配盘明细/Distribution_CNTR_Detail】
    -- 获取 CG_Detail 信息
    if ( b_have_sanme_cg_detail ) then
        -- 【配盘明细】中已经存在相同的 CG_Detail, 更新数量
        local strSetSQL = "F_QTY = F_QTY + ".. distribution_info.qty
        strCondition = "S_DC_NO = '"..distribution_cntr.dc_no.."' AND G_CG_DETAIL_ID = '"..distribution_info.cg_detail_id.."'"
        strCondition = strCondition.." AND S_BS_TYPE = '"..distribution_info.bs_type.."' AND S_BS_NO = '"..distribution_info.bs_no.."'"
 
        nRet, strRetInfo = mobox.updateDataAttrByCondition(strLuaDEID, "Distribution_CNTR_Detail", strCondition, strSetSQL)
        if (nRet ~= 0) then return 1, "设置【配盘明细】 数量失败!"..strRetInfo end        
    else
        local cg_detail
        nRet, cg_detail = m3.GetDataObject( strLuaDEID, "CG_Detail", distribution_info.cg_detail_id )
        if ( nRet ~= 0 ) then return 1, '获取【容器货品明细】对象失败!'..cg_detail end   
 
        distribution_detail = m3.AllocObject(strLuaDEID, "Distribution_CNTR_Detail")
        distribution_detail.dc_no = dc_no
        distribution_detail.cntr_code = cg_detail.cntr_code
        distribution_detail.item_code = cg_detail.item_code
        distribution_detail.item_name = cg_detail.item_name
        distribution_detail.item_spec = cg_detail.item_spec
        distribution_detail.cell_no = cg_detail.cell_no
        distribution_detail.item_state = cg_detail.item_state
        distribution_detail.item_state_name = cg_detail.item_state_name
        distribution_detail.batch_no = cg_detail.batch_no
        distribution_detail.serial_no = cg_detail.serial_no
        distribution_detail.end_user = cg_detail.end_user
        distribution_detail.owner = cg_detail.owner
        distribution_detail.supplier = cg_detail.supplier
        distribution_detail.uom = cg_detail.uom
        distribution_detail.qty = distribution_info.qty
 
        distribution_detail.bs_type = distribution_info.bs_type
        distribution_detail.bs_no = distribution_info.bs_no
        distribution_detail.bs_row_no = distribution_info.bs_row_no
        distribution_detail.cg_detail_id = distribution_info.cg_detail_id
 
        distribution_detail.wh_code = loc.wh_code
        distribution_detail.area_code = loc.area_code
        
        nRet, distribution_detail = m3.CreateDataObj( strLuaDEID, distribution_detail )
        if ( nRet ~= 0 ) then return 1, '创建【配盘明细】对象失败!'..distribution_detail end       
    end
 
    -- 仓库/库区量表+分配量
    local item = {}
    -- 量表变化参数
    item[1] = {
        N_ROW_NO = 1,
        S_ITEM_CODE = distribution_detail.item_code,
        S_ITEM_NAME = distribution_detail.item_name,
        F_QTY = distribution_info.qty
    }
    --  + 仓库/库区分配量
    nRet, strRetInfo = wms.wms_IPA_Start( loc.wh_code, loc.area_code, lua.table2str(item) )
    if ( nRet ~= 0 or strRetInfo == '') then return 1, 'wms_IPA_Start 失败!'..strRetInfo end
    local success
    local pre_alloc_storage
    success, pre_alloc_storage = pcall( json.decode, strRetInfo)
    if ( success == false ) then return 1, "wms_IPA_Start 返回非法的JSON格式!"..pre_alloc_storage end
    local trans_id = pre_alloc_storage.trans_id     -- +分配量 事务标识
    -- 如果没有trans_id说明+分配量失败
    if ( trans_id == nil or trans_id == '') then 
        return 1, "系统在增加仓库/库区分配量时失败! 可能是库存量不足!"
    end
    -- CG_Detail 加分配量
    strCondition = "S_ID = '"..distribution_info.cg_detail_id.."'"
    nRet, strRetInfo = mobox.incDataObjAccQty ( strLuaDEID, "CG_Detail", strCondition, "F_QTY", "F_ALLOC_QTY", distribution_info.qty )
    if ( nRet ~= 0 or strRetInfo ~= '') then 
        wms.wms_IPA_Abort( trans_id )
        return 1, '在增加【容器货品明细】中的分配量时失败!'..strRetInfo 
    end  
    -- 业务来源这里加累计配盘数量
    if ( distribution_info.bs_type == "Shipping_Order") then
        -- 发货单
        strCondition = "S_SHIPPING_NO = '"..distribution_info.bs_no.."' AND N_ROW_NO = "..distribution_info.bs_row_no
        nRet, strRetInfo = mobox.incDataObjAccQty ( strLuaDEID, "Shipping_Detail", strCondition, "F_QTY", "F_ACC_D_QTY", distribution_info.qty )
        if ( nRet ~= 0 or strRetInfo ~= '' ) then 
            wms.wms_IPA_Abort( trans_id )
            return 1, '在增加【发货单明细】中的累计配货数量时失败!'..strRetInfo 
        end     
        nRet, strRetInfo = wms_out.Shipping_Order_CheckState( strLuaDEID, distribution_info.bs_no )  
        if ( nRet ~= 0 ) then 
            wms.wms_IPA_Abort( trans_id )
            return 1, strRetInfo 
        end      
    else
        -- 出库单
    end
 
    wms.wms_IPA_Commit( trans_id )
    return 0
end
 
--[[
    根据输入的出库单编码获取 出库单明细列表,oo_no_set 是一个出库单编码数组,如["OO010","OO012"]
    返回值: 1 -- nRet 0 正确  2 --outbound_detail_list 出库单明细列表 table
]]
function wms_out.Get_Outbound_Detail_list( strLuaDEID, oo_no_set )
    local nRet, strRetInfo
    local where, nCount, n
    local outbound_detail_list = {}
 
    nCount = #oo_no_set
    if ( nCount == 0 ) then
        return 1, "出库单编码不能为空!"
    end
    if ( nCount == 1 ) then
        where = " S_OO_NO = '"..oo_no_set[1].."'"
    else
        where = " S_OO_NO IN ("
        for n = 1, nCount do
            where = where.."'"..oo_no_set[n].."',"
        end
        where = lua.trim_laster_char( where )..")"
    end
 
    local strOrder = "S_ITEM_CODE"
    
    nRet, strRetInfo = mobox.queryDataObjAttr( strLuaDEID, "Outbound_Detail", where, strOrder )
    if ( nRet ~= 0 ) then return 1, "获取【发货单明细】失败! "..strRetInfo end
    -- 如果没有满足条件的出库单明细就直接返回
    if ( strRetInfo == '' ) then return 0, outbound_detail_list end
 
    local retObjs = json.decode( strRetInfo )
    local outbound_detail
 
    for n = 1, #retObjs do
        nRet, outbound_detail = m3.ObjAttrStrToLuaObj( "Outbound_Detail", lua.table2str(retObjs[n].attrs) )   
        table.insert( outbound_detail_list, outbound_detail )
    end 
    return 0, outbound_detail_list
end
 
--[[
    把配盘明细中的货品 批分到每个出库单明细
    输入参数: d_cntr_detail_list [配盘明细]
    返回值: 1 -- nRet 0 正确  2 --outbound_detail_list 出库单明细列表 table
]]
function wms_out.Split_Distribution_CNTR_Detail( strLuaDEID, wave_no, wave_cls_id, d_cntr_detail_list, outbound_detail_list )
    local nRet, strRetInfo
    local n, m, nCount
    local new_d_cntr_detail_list = {}
    local qty
 
    nCount = #outbound_detail_list
    for n = 1, #d_cntr_detail_list do
        qty = d_cntr_detail_list[n].qty
        for m = 1, nCount do
            if ( d_cntr_detail_list[n].item_code == outbound_detail_list[m].item_code  and
                outbound_detail_list[m].qty > outbound_detail_list[m].acc_d_qty ) then
                local d_cntr_detail = {
                    item_code = d_cntr_detail_list[n].item_code,
                    item_name = d_cntr_detail_list[n].item_name,
                    cntr_code = d_cntr_detail_list[n].cntr_code,
                    batch_no = d_cntr_detail_list[n].batch_no,
                    cell_no = d_cntr_detail_list[n].cell_no,
                    station = d_cntr_detail_list[n].station,
 
                    serial_no = d_cntr_detail_list[n].serial_no,
                    item_spec = d_cntr_detail_list[n].item_spec,
                    end_user = d_cntr_detail_list[n].end_user,
                    owner = d_cntr_detail_list[n].owner,        
                    uom = d_cntr_detail_list[n].uom,     
 
                    volume = d_cntr_detail_list[n].volume,  
                    weight = d_cntr_detail_list[n].weight,  
                    
                    wh_code = d_cntr_detail_list[n].wh_code,                 
                    area_code = d_cntr_detail_list[n].area_code,                 
                    loc_code = d_cntr_detail_list[n].loc_code,                 
 
                    cg_detail_id = d_cntr_detail_list[n].cg_detail_id,
                    pick_box_code = "",
                    qty = 0
                }
                -- need_qty  出库单明细 需要批分的货品数量
                -- split_qty 计算出可批分出去的货品数量
                need_qty = outbound_detail_list[m].qty - outbound_detail_list[m].acc_d_qty
                if ( qty > need_qty ) then 
                    split_qty = need_qty
                else
                    split_qty = qty
                end
                outbound_detail_list[m].acc_d_qty = outbound_detail_list[m].acc_d_qty + split_qty
                qty = qty - split_qty
 
                d_cntr_detail.qty = split_qty
                d_cntr_detail.bs_type = "Outbound_Order"
                d_cntr_detail.bs_no = outbound_detail_list[m].oo_no
                d_cntr_detail.bs_row_no = outbound_detail_list[m].row_no
                d_cntr_detail.wave_no = wave_no
                d_cntr_detail.wave_cls_id = wave_cls_id  
 
                table.insert( new_d_cntr_detail_list, d_cntr_detail )
 
                if ( qty == 0 ) then break end
            end
        end
    end
    return new_d_cntr_detail_list
end
 
local function generate_cg_detial_match_sql( item )
    local item_sql = "a.S_ITEM_CODE = '"..item.item_code.."'"
 
    if (item.batch_no ~= nil and item.batch_no ~= '') then
        item_sql = item_sql.." AND a.S_BATCH_NO = '"..item.batch_no.."'"
    end  
    if (item.serial_no ~= nil and item.serial_no ~= '') then
        item_sql = item_sql.." AND a.S_SERIAL_NO = '"..item.serial_no.."'"
    end      
    if (item.supplier ~= nil and item.supplier ~= '') then
        item_sql = item_sql.." AND a.S_SUPPLIER_NO = '"..item.supplier.."'"
    end 
    if (item.owner ~= nil and item.owner ~= '') then
        item_sql = item_sql.." AND a.S_OWNER = '"..item.owner.."'"
    end 
    if (item.item_state ~= nil and type(item.item_state) == "number") then
        item_sql = item_sql.." AND a.N_ITEM_STATE = "..item.item_state
    end 
    if (item.end_user ~= nil and item.end_user ~= '') then
        item_sql = item_sql.." AND a.S_END_USER = '"..item.end_user.."'"
    end  
    if (item.erp_wh_code ~= nil and item.erp_wh_code ~= '') then
        item_sql = item_sql.." AND a.S_ERP_WH_CODE = '"..item.erp_wh_code.."'"
    end  
    if (item.ext_attr1 ~= nil and item.ext_attr1 ~= '') then
        item_sql = item_sql.." AND a.S_EXT_ATTR1 = '"..item.ext_attr1.."'"
    end 
    if (item.ext_attr2 ~= nil and item.ext_attr2 ~= '') then
        item_sql = item_sql.." AND a.S_EXT_ATTR2 = '"..item.ext_attr2.."'"
    end 
    if (item.ext_attr3 ~= nil and item.ext_attr3 ~= '') then
        item_sql = item_sql.." AND a.S_EXT_ATTR3 = '"..item.ext_attr3.."'"
    end 
    if (item.ext_attr4 ~= nil and item.ext_attr4 ~= '') then
        item_sql = item_sql.." AND a.S_EXT_ATTR4 = '"..item.ext_attr4.."'"
    end 
    if (item.ext_attr5 ~= nil and item.ext_attr5 ~= '') then
        item_sql = item_sql.." AND a.S_EXT_ATTR5 = '"..item.ext_attr5.."'"
    end                              
    return item_sql  
end
 
--[[ 起源:巨星料箱配货算法, 可作为标准配货算法
 
  说明: 根据货品编码 item_code 确定货品,根据批次号顺序配盘,批次号小的优先出
输入参数:
    item_list 需要出库的货品清单,其中item中参与查询条件的属性
                {item_code,    batch_no,serial_no,supplier,owner,item_state,end_user, erp_wh_code, ext_attr1,..ext_attr5}
    parameter = {
                    wh_code, area_code 出库货品仓库-库区 , 
                    station  -- 分拣站台, 可以为空
                    exit_loc -- 出库出口货位,货位对象{ area_code, code }
                    bs_type, bs_no 业务来源
                    order_by 匹配CG_Detail时的次序
                }
    match_method: 可以不输入默认为0,0表示会根据 item_list 中出现的 批次号,系列号等进行匹配, 1 -- 表示只对 item_code 进行匹配
返回: 
    d_cntr_list 配盘/Distribution_CNTR  d_cntr_detail_list 配盘明细/Distribution_CNTR_Detail
 
    更改记录:
    V2.0 HAN 20241023
         -- 取消查询时对容器锁的要求(不对容器锁进行查询)
         -- 取消容器加锁(改成作业启动的时候加锁)
    V3.0 HAN 20241027
         加分件站台位置 station
    V4.0 HAN 20241122
         输入参数做了整合 把 wh_code, area_code, station, exit_loc, bs_type, bs_no 整合到 table 参数 parameter
         { wh_code, area_code, station, exit_loc, bs_type, bs_no, order_by }
         其中 order_by 是CG_Detail表中的属性顺序
]]
function wms_out.Distribution_Procedure( strLuaDEID, item_list, parameter, d_cntr_list, d_cntr_detail_list, match_method ) 
    local nRet, strRetInfo, strCondition
    local n, nCount
    local str_where = ''
    local strOrder = ''
 
    lua.Debug( strLuaDEID, debug.getinfo(1), "wms_out.Distribution_Procedure", parameter )
 
    -- step1:输入参数判断,并且组织仓库查询条件
    if ( parameter == nil or parameter == '') then return 1, "输入参数错误, wh_code必须有值"  end
    if ( parameter.wh_code == nil or parameter.wh_code == '' ) then
        return 1, "输入参数错误, parameter.wh_code必须有值"
    end
    str_where = "S_WH_CODE = '"..parameter.wh_code.."'"
    if ( parameter.area_code ~= nil and parameter.area_code ~= '') then
        str_where = str_where.." AND S_AREA_CODE = '"..parameter.area_code.."'"
    end    
    if ( parameter.exit_loc == nil or parameter.exit_loc == '' ) then
        return 1, "输入参数错误, parameter.exit_loc 必须有值"
    end    
    if ( match_method == nil or match_method == '' ) then match_method = 0 end
 
    strTable = "TN_CG_Detail a LEFT JOIN TN_Container b ON a.S_CNTR_CODE = b.S_CODE"       -- 联表
    strAttrs = "a.S_CNTR_CODE, a.S_CELL_NO, a.S_BATCH_NO, a.F_QTY, a.F_ALLOC_QTY, a.S_ID, b.S_POSITION,"..
               "a.S_SERIAL_NO, a.N_ITEM_STATE, a.S_END_USER, a.S_OWNER, a.S_SUPPLIER_NO,a.S_ERP_WH_CODE,"..
               "a.S_EXT_ATTR1,a.S_EXT_ATTR2,a.S_EXT_ATTR3,a.S_EXT_ATTR4,a.S_EXT_ATTR5"
    if ( parameter.order_by ~= nil and parameter.order_by ~= '') then
        strOrder = "a."..parameter.order_by
    end
    local qty, alloc_qty, need_qty
    local bFind
    local item_condition, cg_detail_id, cell_no, cntr_loc_code
    local batch_no, serial_no, item_state, end_user, owner, supplier, erp_wh_code
    local ext_attr1, ext_attr2, ext_attr3, ext_attr4, ext_attr5
 
    -- step2: 遍历待出库货品清单
    for n = 1, #item_list do
        -- step2.1 组织查询条件
        -- CG_Detail中的货品物料编码一样,容器可以使用
        -- V2.0 改进查询条件
        if ( match_method == 1 ) then
            item_condition = "a.S_ITEM_CODE = '"..item_list[n].item_code.."'"
        else
            item_condition = generate_cg_detial_match_sql( item_list[n] )
        end
        --strCondition = item_condition.." AND b.C_ENABLE = 'Y' AND b.N_LOCK_STATE = 0 AND a.F_QTY > a.F_ALLOC_QTY "..
        --               "AND a.S_CNTR_CODE IN (select S_CNTR_CODE from TN_Loc_Container where S_LOC_CODE IN (select S_CODE from TN_Location where "..str_where..")) "
        -- MDF BY WHB @20250110 在配盘时取消对容器锁的限制
        strCondition = item_condition.." AND b.C_ENABLE = 'Y' AND a.F_QTY > a.F_ALLOC_QTY "..
        "AND a.S_CNTR_CODE IN (select S_CNTR_CODE from TN_Loc_Container where S_LOC_CODE IN (select S_CODE from TN_Location where "..str_where..")) "
 
        lua.Debug( strLuaDEID, debug.getinfo(1), "strCondition", strCondition )
 
        -- step2.2 多表联查获取容器货品信息
        nRet, strRetInfo = mobox.queryMultiTable(strLuaDEID, strAttrs, strTable, 1000, strCondition, strOrder )  
 
        if ( nRet ~= 0 ) then return 2, "查询【容器货品明细】信息失败! " .. strRetInfo  end
        if ( strRetInfo == '' ) then
            lua.Debug( strLuaDEID, debug.getinfo(1), "查询条件 = ", strCondition )
            return 1, "货品编码 = '"..item_list[n].item_code.."' 没找到库存! 错误编码:5001"
        end
        cg_detail_set = json.decode(strRetInfo)  
        -- step2.3 根据出库数量遍历2.2获取的容器货品信息进行数量批分
        -- item_list[n].qty --> 需要出库的数量  item_list[n].alloc_qty --> 已经配货数量
        for m = 1, #cg_detail_set do
            -- step2.3.1 配盘数量已经到达出库数量
 
            if ( lua.equation( item_list[n].qty, item_list[n].alloc_qty ) ) then break end
            -- step2.3.2 获取容器货品记录里的一些基本属性
            cntr_code = cg_detail_set[m][1]
            cell_no = cg_detail_set[m][2]
            batch_no = cg_detail_set[m][3]
            -- 容器中货品数量
            qty = lua.StrToNumber( cg_detail_set[m][4] )
            -- 容器中已经被分配的数量
            alloc_qty = lua.StrToNumber( cg_detail_set[m][5] )
            cg_detail_id = cg_detail_set[m][6]
            cntr_loc_code = cg_detail_set[m][7]                          -- 容器所在货位
            --
            serial_no = cg_detail_set[m][8]            
            item_state = lua.StrToNumber( cg_detail_set[m][9] )
            end_user = cg_detail_set[m][10]
            owner = cg_detail_set[m][11]
            supplier = cg_detail_set[m][12]
            erp_wh_code = cg_detail_set[m][13]
            ext_attr1 = cg_detail_set[m][14]
            ext_attr2 = cg_detail_set[m][15]
            ext_attr3 = cg_detail_set[m][16]
            ext_attr4 = cg_detail_set[m][17]
            ext_attr5 = cg_detail_set[m][18]
 
            -- 可用于配货的数量
            qty = qty - alloc_qty
            -- 还有多少货品没配
            need_qty = item_list[n].qty - item_list[n].alloc_qty   
 
            -- 把容器加入 配货容器 清单
            -- 初始化【配盘】容器信息
            local distribution_cntr = {
                cntr_code = cntr_code,
                b_state = 1,
                wh_code = parameter.wh_code,
                area_code = parameter.area_code,
                loc_code = cntr_loc_code,                
                bs_type = parameter.bs_type,
                bs_no = parameter.bs_no,
                exit_area_code = parameter.exit_loc.area_code,
                exit_loc_code = parameter.exit_loc.code,
                station = parameter.station,
                dc_no = ""                          -- 配盘号
            }
 
            -- step2.3.3  检查一下容器是否已经在 d_cntr_list, 如果不存在要把 容器加入 d_cntr_list
            bFind = false
            for  i = 1, #d_cntr_list do
                if ( d_cntr_list[i].cntr_code == distribution_cntr.cntr_code ) then
                    bFind = true 
                    break 
                end
            end
            if ( bFind == false ) then
                table.insert( d_cntr_list, distribution_cntr )
            end
 
            -- step2.3.4 批分容器配盘数量
            if ( need_qty > qty ) then
                d_qty = qty
            else
                d_qty = need_qty
            end
            if ( d_qty == 0 ) then break end
            item_list[n].alloc_qty = item_list[n].alloc_qty + d_qty
 
            -- step2.3.5 生成 配盘明细并且加入配盘明细清单
            local d_cntr_detail = {
                cntr_code = cntr_code,
                cell_no = cell_no,
                batch_no = batch_no,
                item_code = item_list[n].item_code,
                item_name = item_list[n].item_name,
                serial_no = serial_no, item_state = item_state, end_user = end_user, owner = owner, supplier = supplier, erp_wh_code = erp_wh_code,
                ext_attr1 = ext_attr1, ext_attr2 = ext_attr2, ext_attr3 = ext_attr3, ext_attr4 = ext_attr4, ext_attr5 = ext_attr5,
 
                volume = item_list[n].volume,
                weight = item_list[n].weight,
                bs_type = parameter.bs_type,
                bs_no = parameter.bs_no,
                bs_row_no = n,                  -- 货品清单中的顺序号
                wave_cls_id = "",
                wave_no = "",
                qty = d_qty,
                station = parameter.station,
                cg_detail_id = cg_detail_id,
                pick_box_code = ""              -- 拣料箱编码
            }
            table.insert( d_cntr_detail_list, d_cntr_detail )
        end          
    end
 
    return 0
end
 
--[[
    创建配盘及配盘明细数据对象
    参数: d_cntr_list  【配盘】链表 [{cntr_code, bs_type, bs_no, wh_code, area_code, exit_area_code, exit_loc_code }]
    d_cntr_detail_list【配盘明细】链表 [{item_code, item_name, batch_no, serial_no, cntr_code, cell_no, item_state, uom, qty, bs_type, bs_no, wave_cls_id, wave_no, cg_detail_id }]
    返回值 nRet = 0 表示创建成功
]]
function wms_out.Creat_Distribution_list( strLuaDEID, d_cntr_list, d_cntr_detail_list  )
    local nRet, strRetInfo, n
 
    local distribution_cntr
    local nCount = #d_cntr_list
 
    for n = 1, nCount do
        distribution_cntr = m3.AllocObject(strLuaDEID, "Distribution_CNTR")
        distribution_cntr.cntr_code = d_cntr_list[n].cntr_code
        distribution_cntr.b_state = d_cntr_list[n].b_state
        distribution_cntr.wh_code = d_cntr_list[n].wh_code
        distribution_cntr.area_code = d_cntr_list[n].area_code
        distribution_cntr.loc_code = d_cntr_list[n].loc_code
        distribution_cntr.bs_type = d_cntr_list[n].bs_type
        distribution_cntr.bs_no = d_cntr_list[n].bs_no    
        distribution_cntr.station = d_cntr_list[n].station        
        distribution_cntr.exit_area_code = d_cntr_list[n].exit_area_code
        distribution_cntr.exit_loc_code = d_cntr_list[n].exit_loc_code
 
        nRet, distribution_cntr = m3.CreateDataObj( strLuaDEID, distribution_cntr )
        if ( nRet ~= 0 ) then return 1, '创建【配盘】对象失败!'..distribution_cntr end
        d_cntr_list[n].dc_no = distribution_cntr.dc_no
    end    
 
    local dc_no, wh_code, area_code, loc_code
    local strCondition
    local distribution_detail
 
    -- step3 创建【配盘明细/Distribution_CNTR_Detail】
    -- 获取 CG_Detail 信息
    for m = 1, #d_cntr_detail_list do
 
        -- 通过容器编码获取 配盘号
        dc_no = ""
        for n = 1, nCount do 
            if ( d_cntr_list[n].cntr_code == d_cntr_detail_list[m].cntr_code ) then
                dc_no = d_cntr_list[n].dc_no
                wh_code = d_cntr_list[n].wh_code
                area_code = d_cntr_list[n].area_code
                loc_code = d_cntr_list[n].loc_code
                break
            end
        end
        if ( dc_no == '' ) then
            return 1, "容器'"..d_cntr_detail_list[m].cntr_code.."' 无法定位到【配盘】!"
        end
 
        distribution_detail = m3.AllocObject(strLuaDEID, "Distribution_CNTR_Detail")
        distribution_detail.dc_no = dc_no
        distribution_detail.cntr_code = d_cntr_detail_list[m].cntr_code
        distribution_detail.item_code = lua.Get_StrAttrValue( d_cntr_detail_list[m].item_code )
        distribution_detail.item_name = lua.Get_StrAttrValue( d_cntr_detail_list[m].item_name )
        distribution_detail.item_spec = lua.Get_StrAttrValue( d_cntr_detail_list[m].item_spec )
        distribution_detail.cell_no = lua.Get_StrAttrValue( d_cntr_detail_list[m].cell_no )
        distribution_detail.item_state = lua.Get_NumAttrValue( d_cntr_detail_list[m].item_state )
        distribution_detail.batch_no = lua.Get_StrAttrValue( d_cntr_detail_list[m].batch_no )
        distribution_detail.serial_no = lua.Get_StrAttrValue( d_cntr_detail_list[m].serial_no )
        distribution_detail.end_user = lua.Get_StrAttrValue( d_cntr_detail_list[m].end_user )
        distribution_detail.owner = lua.Get_StrAttrValue( d_cntr_detail_list[m].owner )
        distribution_detail.supplier = lua.Get_StrAttrValue( d_cntr_detail_list[m].supplier )
        distribution_detail.erp_wh_code = lua.Get_StrAttrValue( d_cntr_detail_list[m].erp_wh_code )
 
        distribution_detail.ext_attr1 = lua.Get_StrAttrValue( d_cntr_detail_list[m].ext_attr1 ) 
        distribution_detail.ext_attr2 = lua.Get_StrAttrValue( d_cntr_detail_list[m].ext_attr2 ) 
        distribution_detail.ext_attr3 = lua.Get_StrAttrValue( d_cntr_detail_list[m].ext_attr3 ) 
        distribution_detail.ext_attr4 = lua.Get_StrAttrValue( d_cntr_detail_list[m].ext_attr4 ) 
        distribution_detail.ext_attr5 = lua.Get_StrAttrValue( d_cntr_detail_list[m].ext_attr5 )        
 
        distribution_detail.uom = lua.Get_StrAttrValue( d_cntr_detail_list[m].uom )
        distribution_detail.qty = lua.Get_NumAttrValue( d_cntr_detail_list[m].qty )
        distribution_detail.bs_type = d_cntr_detail_list[m].bs_type
        distribution_detail.bs_no = d_cntr_detail_list[m].bs_no
        distribution_detail.bs_row_no = d_cntr_detail_list[m].bs_row_no
        distribution_detail.cg_detail_id = d_cntr_detail_list[m].cg_detail_id
        distribution_detail.wave_no = d_cntr_detail_list[m].wave_no
        distribution_detail.wave_cls_id = d_cntr_detail_list[m].wave_cls_id
        distribution_detail.wh_code = wh_code
        distribution_detail.area_code = area_code
        distribution_detail.loc_code = loc_code
        distribution_detail.station = d_cntr_detail_list[m].station
        distribution_detail.pick_box_code = d_cntr_detail_list[m].pick_box_code
        nRet, distribution_detail = m3.CreateDataObj( strLuaDEID, distribution_detail )
 
        if ( nRet ~= 0 ) then return 1, '创建【配盘明细】对象失败!'..distribution_detail end  
             
    end
    return 0
end
 
 
-- 把出库单明细中的 item 加入出库明细汇总链表 sum_outbound_detail, 如果 货品编码相同 数量相加
-- strOONo 出库单号
-- sum_outbound_detail 出库单波次明细 [{item_name,item_code,qty,weight,volume,cell_type}]
-- 返回:nRet = 0  outbound_detail_info -- 出库单明细汇总信息
-- outbound_detail_info { total_qty, total_weight, total_volume }
function wms_out.Sum_outbound_detail( strLuaDEID, strOONo, sum_outbound_detail )
    local nRet, strRetInfo
    local strCondition
    local data_objs
    local outbound_detail_info = {
        total_qty = 0, total_weight = 0, total_volume = 0
    }
 
    strCondition = "S_OO_NO = '"..strOONo.."'"
    nRet, data_objs = m3.QueryDataObject(strLuaDEID, "Outbound_Detail", strCondition, "N_ROW_NO" )
    if (nRet ~= 0) then return 2, "QueryDataObject失败!"..data_objs end
    if ( data_objs == '' ) then return 0, outbound_detail_info  end
    
    local n, item_code, qty, weight, volume
    local detail_attrs
    local bFind
 
    for n = 1, #data_objs do
        detail_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
        item_code = lua.Get_StrAttrValue( detail_attrs.S_ITEM_CODE )
        qty = lua.StrToNumber( detail_attrs.F_QTY )
        weight = lua.StrToNumber( detail_attrs.F_WEIGHT )
        volume = lua.StrToNumber( detail_attrs.F_VOLUME )
 
        if ( item_code ~= '' and qty > 0 ) then
 
            outbound_detail_info.total_qty = outbound_detail_info.total_qty + qty
            outbound_detail_info.total_weight = outbound_detail_info.total_weight + qty*weight
            outbound_detail_info.total_volume = outbound_detail_info.total_volume + qty*volume
 
            bFind = false
            for m = 1, #sum_outbound_detail do
                if (sum_outbound_detail[m].item_code == item_code) then
                    bFind = true
                    sum_outbound_detail[m].qty = sum_outbound_detail[m].qty + qty
                    break
                end
            end
            if ( bFind == false ) then
                local out_item = {
                    item_code = item_code,
                    item_name = lua.Get_StrAttrValue( detail_attrs.S_ITEM_NAME ),
                    qty = qty,
                    alloc_qty = 0,
                    weight = weight,
                    volume = volume,
                    cell_type = lua.Get_StrAttrValue( detail_attrs.S_CELL_TYPE )
                }
                table.insert( sum_outbound_detail, out_item )
            end
        end
    end
    return 0, outbound_detail_info
end
 
 
--[[ 
    分拣出库后,根据配盘明细中的分拣结果 影响 量表,CG_Detail
    [配盘明细 /Distribution_CNTR_Detail] 的后处理程序
    --减仓库/库区量表的分配量
    --减仓库/库区量表的存储量
    --生成【OnOff_Shelves】上下架记录
    --减 CG_Detail 中的QTY个ALLOC_QTY
    --更新 来源单号 中的累计出库数量
]]
function wms_out.Distribution_CNTR_Detail_PostProcess( strLuaDEID, wh_code, area_code, loc_code, dc_no )
    local nRet, strRetInfo
 
    if ( dc_no == nil or dc_no == '' ) then 
        return 1, "wms_out.Distribution_CNTR_Detail_PostProcess 函数中 dc_no 必须有值!" 
    end
 
    local chg_target = ''       -- 量表变化对象
    if ( wh_code == '' or wh_code == nil ) then 
        return 1, "wms_out.Distribution_CNTR_Detail_PostProcess 函数中仓库编码必须有值!"
    end
    if ( area_code == nil ) then area_code = '' end
    if ( loc_code == nil ) then loc_code = '' end
 
    -- 获取 Organize_CNTR_Detail
    local strOrder = ''
    local strCondition = "S_DC_NO = '"..dc_no.."'"
 
    nRet, strRetInfo = mobox.queryDataObjAttr( strLuaDEID, "Distribution_CNTR_Detail", strCondition, strOrder )
    if ( nRet ~= 0 ) then return 1, "获取【配盘明细】失败! "..strRetInfo end
    if ( strRetInfo == '' ) then 
        lua.Warning( strLuaDEID, debug.getinfo(1), "配盘号'"..dc_no.."'的明细为空!" )
        return 1, "配盘号'"..dc_no.."'的明细为空!"
    end
 
    local retObjs = json.decode( strRetInfo )
    local n
    local inventory_change = {}     -- 存储量变化
    local alloc_qty_change = {}     -- 分配量变化
    local dc_detail = {}
    local onoff_shelves = {}        -- 上下架记录
    local days = os.date("%Y%m%d")
    local cancel_qty                -- 取消数量
 
    -- 获取存储量变化数据 并且创建 上下架记录
    for n = 1, #retObjs do
        nRet, dc_detail = m3.ObjAttrStrToLuaObj( "Distribution_CNTR_Detail", lua.table2str(retObjs[n].attrs) )
        if ( nRet ~= 0 ) then return 1, "m3.ObjAttrStrToLuaObj(Organize_CNTR_Detail) 失败! "..dc_detail end
 
        -- 强制完成产生的取消数量
        cancel_qty = dc_detail.qty - dc_detail.acc_p_qty        
        if ( dc_detail.acc_p_qty > 0 ) then
            -- 生成仓库量变化输入参数
            local inventory_info = {
                item_code = dc_detail.item_code,
                item_name = dc_detail.item_name,
                qty = dc_detail.acc_p_qty,
                wh_code = wh_code,
                area_code = area_code
            }
            table.insert( inventory_change, inventory_info )
 
            -- 生成分配量变化输入参数
            local alloc_qty_info = {
                item_code = dc_detail.item_code,
                item_name = dc_detail.item_name,
                qty = dc_detail.qty,
                wh_code = wh_code,
                area_code = area_code
            }
            table.insert( alloc_qty_change, alloc_qty_info )        
 
            -- 生成上架记录【OnOff_Shelves】
            onoff_shelves = m3.AllocObject(strLuaDEID,"OnOff_Shelves")
            onoff_shelves.action = "-"
            onoff_shelves.d_action = days
 
            onoff_shelves.wh_code = wh_code
            onoff_shelves.area_code = area_code
            onoff_shelves.loc_code = loc_code
            
            onoff_shelves.item_code = dc_detail.item_code
            onoff_shelves.item_name = dc_detail.item_name  
            onoff_shelves.batch_no = dc_detail.batch_no
            onoff_shelves.serial_no = dc_detail.serial_no
            onoff_shelves.item_spec = dc_detail.item_spec
            onoff_shelves.item_state_name = dc_detail.item_state_name
            onoff_shelves.item_state = dc_detail.item_state
 
            onoff_shelves.end_user = dc_detail.end_user
            onoff_shelves.owner = dc_detail.owner
            onoff_shelves.supplier = dc_detail.supplier
            onoff_shelves.supplier_name = dc_detail.supplier_name        
 
            onoff_shelves.cntr_code = dc_detail.cntr_code
            onoff_shelves.cell_no = dc_detail.cell_no
 
            onoff_shelves.qty = dc_detail.acc_p_qty
            onoff_shelves.uom = dc_detail.uom
 
            onoff_shelves.bs_no = dc_detail.bs_no
            onoff_shelves.bs_type = dc_detail.bs_type        
        
            nRet, onoff_shelves = m3.CreateDataObj( strLuaDEID, onoff_shelves )
            if ( nRet ~= 0 ) then 
                return 1, 'mobox 创建【上下架记录】对象失败!'..onoff_shelves
            end   
 
            -- 减 CG_Detail 中的QTY
            if ( dc_detail.acc_p_qty > 0 ) then
                nRet, strRetInfo = wms_cntr.Reduc_CG_Detail_Qty_AlloccQty( strLuaDEID, dc_detail.cg_detail_id, dc_detail.acc_p_qty, dc_detail.qty )
                if ( nRet ~= 0 )  then return 1, "wms_cntr.Reduc_CG_Detail_Qty_AlloccQty 失败! "..strRetInfo end
            end
        end
        -- 如果来源类型 = Outbound_Order 更新出库单的累计出库数量
        if ( dc_detail.bs_type == "Outbound_Order" ) then
            if ( dc_detail.bs_no ~= nil and dc_detail.bs_no ~= '' ) then
                strCondition = "S_OO_NO = '"..dc_detail.bs_no.."' AND S_ITEM_CODE = '"..dc_detail.item_code.."'"
                local strSetAttr = "F_ACC_O_QTY = F_ACC_O_QTY +"..dc_detail.acc_p_qty
                if ( lua.equation( 0, cancel_qty ) == false ) then
                    strSetAttr = strSetAttr..", F_ACC_C_QTY = F_ACC_C_QTY + "..cancel_qty
                end                    
                nRet, strRetInfo = mobox.updateDataAttrByCondition(strLuaDEID, "Outbound_Detail", strCondition, strSetAttr )
                if (nRet ~= 0) then return 1, "设置【Outbound_Detail】累计出库数量失败!"..strRetInfo end
            end
        end
        if ( dc_detail.wave_cls_id == 'Outbound_Wave' ) then
            if ( dc_detail.wave_no ~= nil and dc_detail.wave_no ~= '' ) then
                strCondition = "S_WAVE_NO = '"..dc_detail.wave_no.."' AND S_ITEM_CODE = '"..dc_detail.item_code.."'"
                local strSetAttr = "F_ACC_O_QTY = F_ACC_O_QTY +"..dc_detail.acc_p_qty
                if ( lua.equation( 0, cancel_qty ) == false ) then
                    strSetAttr = strSetAttr..", F_ACC_C_QTY = F_ACC_C_QTY + "..cancel_qty
                end                        
                nRet, strRetInfo = mobox.updateDataAttrByCondition(strLuaDEID, "OW_Detail", strCondition, strSetAttr )
                if (nRet ~= 0) then return 1, "设置【OW_Detail】累计出库数量失败!"..strRetInfo end                
            end
        end        
    end    
 
    -- 存储量处理
    if ( #inventory_change > 0 ) then
        local str_inventory_change = lua.table2str( inventory_change )
        if ( wh_code ~= '' and wh_code ~= nil ) then
            -- 8 减仓库存储量
            nRet, strRetInfo = wms.wms_AddWHInventoryChange(strLuaDEID, 8, str_inventory_change )
            if ( nRet ~= 0 ) then return 1, "wms_AddWHInventoryChange 失败! "..strRetInfo end
        end
 
        if ( area_code ~= '' and area_code ~= nil ) then
            -- 9 减库区存储量
            nRet, strRetInfo = wms.wms_AddWHInventoryChange(strLuaDEID, 9, str_inventory_change )
            if ( nRet ~= 0 ) then return 1, "wms_AddWHInventoryChange 失败! "..strRetInfo end
        end        
    end  
    -- 分配量处理
    if ( #alloc_qty_change > 0 ) then
        local str_alloc_qty_change = lua.table2str( alloc_qty_change )
        if ( wh_code ~= '' and wh_code ~= nil ) then
            -- 10 减仓库分配量
            nRet, strRetInfo = wms.wms_AddWHInventoryChange(strLuaDEID, 10, str_alloc_qty_change )
            if ( nRet ~= 0 ) then return 1, "wms_AddWHInventoryChange 失败! "..strRetInfo end
        end
 
        if ( area_code ~= '' and area_code ~= nil ) then
            -- 11 减库区分配量
            nRet, strRetInfo = wms.wms_AddWHInventoryChange(strLuaDEID, 11, str_alloc_qty_change )
            if ( nRet ~= 0 ) then return 1, "wms_AddWHInventoryChange 失败! "..strRetInfo end
        end        
    end   
    return 0  
end
 
local function create_so_cntr_detail( strLuaDEID, station, cntr_code, so_no, soc_no, item_code )
    local nRet, strRetInfo
    local obj_attrs, data_objs
    local strCondition = "S_CNTR_CODE = '"..cntr_code.."'"
    local so_cntr_detail
 
    if ( item_code ~= nil and item_code ~= '' ) then
        strCondition = strCondition.." AND S_ITEM_CODE = '"..item_code.."'"
    end
 
    nRet, data_objs = m3.QueryDataObject( strLuaDEID, "CG_Detail", strCondition )
    if ( nRet ~= 0 ) then return 1, "获取【CG_Detail】失败! "..data_objs end
    if ( data_objs == '' ) then return 0 end
 
    for n = 1, #data_objs do
        obj_attrs = m3.KeyValueAttrsToObjAttr(data_objs[n].attrs)
 
        so_cntr_detail = m3.AllocObject(strLuaDEID,"SO_CNTR_Detail")
        so_cntr_detail.soc_no = soc_no
        so_cntr_detail.station = station
        so_cntr_detail.so_no = so_no
        so_cntr_detail.cntr_code = cntr_code
        so_cntr_detail.qty = lua.Get_NumAttrValue( obj_attrs.F_QTY )
        so_cntr_detail.cell_no = obj_attrs.S_CELL_NO
        so_cntr_detail.item_code = obj_attrs.S_ITEM_CODE
        so_cntr_detail.item_name = obj_attrs.S_ITEM_NAME
        so_cntr_detail.item_state = lua.StrToNumber( obj_attrs.N_ITEM_STATE )
        so_cntr_detail.batch_no = obj_attrs.S_BATCH_NO  
        so_cntr_detail.serial_no = obj_attrs.S_SERIAL_NO  
        so_cntr_detail.erp_wh_code = obj_attrs.S_ERP_WH_CODE  
        so_cntr_detail.end_user = obj_attrs.S_END_USER  
        so_cntr_detail.owner = obj_attrs.S_OWNER  
        so_cntr_detail.supplier = obj_attrs.S_SUPPLIER_NO   
        
        so_cntr_detail.uom = obj_attrs.S_UOM        
        so_cntr_detail.ext_attr1 = obj_attrs.S_EXT_ATTR1      
        so_cntr_detail.ext_attr2 = obj_attrs.S_EXT_ATTR2 
        so_cntr_detail.ext_attr3 = obj_attrs.S_EXT_ATTR3 
        so_cntr_detail.ext_attr4 = obj_attrs.S_EXT_ATTR4 
        so_cntr_detail.ext_attr5 = obj_attrs.S_EXT_ATTR5                                                       
 
        nRet, so_cntr_detail = m3.CreateDataObj( strLuaDEID, so_cntr_detail )
        if ( nRet ~= 0 ) then 
            return 1, 'mobox 创建【指定出库容器货品明细】对象失败!'..so_cntr_detail
        end         
    end  
    return 0  
end
 
-- 根据指(so)定出库货品,创建指定出库容器和 指定出库容器明细
local function create_so_cntr_operation( strLuaDEID, station, so_no, cntr_code, to_loc, item_code, source_sys )
    local nRet, strRetInfo
 
    if ( source_sys == nil ) then source_sys = "" end
    -- 获取容器所在的货位
    local from_loc_code = wms_wh.GetLocCodeByCNTR( strLuaDEID, cntr_code )    
    if  ( from_loc_code == '' ) then return 1, "容器'"..cntr_code.."'无法获取货位!" end
    local from_loc
    nRet, from_loc = wms_wh.GetLocInfo( from_loc_code )
    if ( nRet ~= 0 ) then return 1, '获取货位信息失败! '..from_loc end       
 
    -- 创建 指定出库容器
    local so_cntr = m3.AllocObject(strLuaDEID,"Specify_Outbound_CNTR")
 
    so_cntr.so_no = so_no
    so_cntr.cntr_code = cntr_code
    so_cntr.wh_code = from_loc.wh_code
    so_cntr.area_code = from_loc.area_code
    so_cntr.loc_code = from_loc.code
    so_cntr.to_loc_code = to_loc.code
    so_cntr.station = station
    nRet, so_cntr = m3.CreateDataObj( strLuaDEID, so_cntr )   
    if (nRet ~= 0) then return 1, "创建【盘点单】失败!"..so_cntr end  
    
    nRet, strRetInfo = create_so_cntr_detail( strLuaDEID, station, cntr_code, so_no, so_cntr.soc_no, item_code )
    if ( nRet ~= 0 ) then return nRet, strRetInfo end
 
    local operation = m3.AllocObject(strLuaDEID,"Operation")
    operation.source_sys = source_sys
    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.lock_cntr = "N"                                   -- 作业启动时不需要锁容器
    operation.cntr_code = cntr_code
 
    operation.op_type = wms_base.Get_nConst( strLuaDEID, "作业类型-出库" )
    operation.op_def_name = "指定出库"
    operation.bs_type = "Specify_Outbound"
    operation.bs_no = so_no
    nRet, operation = m3.CreateDataObj( strLuaDEID, operation )
    if ( nRet ~= 0 ) then return 2, '创建【作业】失败!'..operation end  
 
    -- 容器加出库锁/2 -- 加内存同时加数据库
    nRet, strRetInfo = wms_cntr.Lock( strLuaDEID, cntr_code, 2, so_no )
    if ( nRet ~= 0 ) then
        return 2, "给容器'"..cntr_code.."'加出库锁失败!"
    end 
 
    return 0
end
 
--[[ 
    根据容器号创建一个指定出库作业
    SOO -- Specify Outbound Operation
    输入参数:
    so_no       -- 指定出库指令号
    cntr_code   -- 容器编码
    to_loc_code -- 出库口货位
    op_def_name -- 作业类型
    source_sys  -- 来源系统
--]]
function wms_out.Create_SOO_ByContainer( strLuaDEID, station, so_no, cntr_code, to_loc_code, op_def_name, source_sys )
    local nRet, strRetInfo, n
 
    -- step1:输入参数合法性检查
    if ( so_no == nil or so_no == '') then return 1, "so_no 必须有值!" end    
    if ( cntr_code == nil or cntr_code == '') then return 1, "cntr 必须有值!" end
    if ( to_loc_code == nil or to_loc_code == '') then return 1, "loc_code 必须有值!" end
    if ( station == nil or station == '') then return 1, "station 必须有值!" end
    if ( source_sys == nil ) then source_sys = "" end
    -- 判断目标货位是否正确
    local to_loc
    nRet, to_loc = wms_wh.GetLocInfo( to_loc_code )
    if ( nRet ~= 0 ) then return 1, '获取货位信息失败! '..to_loc end   
 
    -- 创建指定出库容器+容器明细+作业
    nRet, strRetInfo = create_so_cntr_operation( strLuaDEID, station, so_no, cntr_code, to_loc, "", source_sys )
    if ( nRet ~= 0 ) then 
        wms.wms_AbortCntrLockTrans( so_no ) 
        return nRet, strRetInfo 
    end   
    
    -- 设置 Specify_Outbound 状态为执行 N_B_STATE = 2 执行中
    strUpdateSql = "N_B_STATE = 2, N_CNTR_TOTAL = 1"
    strCondition = "S_SO_NO = '"..so_no.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Specify_Outbound", strCondition, strUpdateSql )
    if ( nRet ~= 0 ) then  
        wms.wms_AbortCntrLockTrans( so_no ) 
        return 2, "更新【Specify_Outbound】信息失败!"..strRetInfo
    end  
    -- create_so_cntr_operation 有锁容器操作
    wms.wms_CommitCntrLockTrans( so_no ) 
 
 
    return 0
end
 
--[[ 
    根据物料/货品号创建指定出库作业(可能会有多个容器)
    SOO -- Specify Outbound Operation
    输入参数:
    so_no       -- 指定出库指令号
    item_code   -- 物料货品编码
    to_loc_code -- 出库口货位
    op_def_name -- 作业类型
    source_sys  -- 来源系统
--]]
function wms_out.Create_SOO_ByMaterial( strLuaDEID, station, so_no, area_code, item_code, to_loc_code, op_def_name, source_sys )
    local nRet, strRetInfo, n
 
    -- step1:输入参数合法性检查
    if ( so_no == nil or so_no == '') then return 1, "so_no 必须有值!" end  
    if ( area_code == nil or area_code == '') then return 1, "area_code 必须有值!" end  
    if ( source_sys == nil ) then source_sys = "" end
    if ( item_code == nil or item_code == '') then return 1, "item_code 必须有值!" end
    if ( to_loc_code == nil or to_loc_code == '') then return 1, "loc_code 必须有值!" end
    if ( station == nil or station == '') then return 1, "station 必须有值!" end    
 
 
    -- 判断目标货位是否正确
    local to_loc
    nRet, to_loc = wms_wh.GetLocInfo( to_loc_code )
    if ( nRet ~= 0 ) then return 1, '获取货位信息失败! '..to_loc end   
 
    -- 通过货品找出货品所在容器
    local str_good_condition
    local success, queryInfo, dataSet
    local nPage, nPageCount
    local cntr_code
 
    str_good_condition = "S_ITEM_CODE = '"..item_code.."' AND S_CNTR_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.."'))"
 
    -- 查询有该货品的容器编号
    -- 获取货品所在容器(考虑到有比较极端情况容器数量大于1000因此采用 queryDataObjAttr2 )        
    nRet, strRetInfo = mobox.queryDataObjAttr2( strLuaDEID, "CG_Detail", str_good_condition, strOrder, 100, "S_CNTR_CODE" )
    if ( nRet ~= 0 ) then return 1, "queryDataObjAttr2: "..strRetInfo end  
    if  ( strRetInfo == '' ) then return 0 end
 
    lua.Debug( strLuaDEID, debug.getinfo(1), "货品所在容器 -- >", strRetInfo ) 
 
    success, queryInfo = pcall( json.decode, strRetInfo )
    if ( success == false ) then return 2, "queryDataObjAttr2 返回结果啊非法的JSON格式!" end
 
    queryID = queryInfo.queryID
    nPageCount = queryInfo.pageCount
    nPage = 1
    dataSet = queryInfo.dataSet       -- 查询出来的数据集
    local count = 0
    while (nPage <= nPageCount) do
        for i = 1, #dataSet do
            cntr_code = dataSet[i].attrs[1].value
            nRet, strRetInfo = create_so_cntr_operation( strLuaDEID, station, so_no, cntr_code, to_loc, item_code, source_sys )
            if ( nRet ~= 0 )  then  
                wms.wms_AbortCntrLockTrans( so_no ) 
                return 1, "create_so_cntr_operation! ".. strRetInfo 
            end
            count = count + 1
        end
 
        nPage = nPage + 1
        if ( nPage <= nPageCount ) then
            -- 取下一页
            nRet, strRetInfo = mobox.queryDataObjAttr2( queryID, nPage)
            if ( nRet ~= 0 ) then
                wms.wms_AbortCntrLockTrans( so_no ) 
                return 2, "queryDataObjAttr2失败! nPage="..nPage.."  "..strRetInfo
            end 
            queryInfo = json.decode(strRetInfo) 
            dataSet = queryInfo.dataSet 
        end
    end    
 
 
    -- 设置 Specify_Outbound 状态为执行 N_B_STATE = 2 执行中
    strUpdateSql = "N_B_STATE = 2, N_CNTR_TOTAL = "..count
    strCondition = "S_SO_NO = '"..so_no.."'"
    nRet, strRetInfo = mobox.updateDataAttrByCondition( strLuaDEID, "Specify_Outbound", strCondition, strUpdateSql )
    if ( nRet ~= 0 ) then  
        wms.wms_AbortCntrLockTrans( so_no ) 
        return 2, "更新【Specify_Outbound】信息失败!"..strRetInfo
    end      -- create_so_cntr_operation 有锁容器操作
    wms.wms_CommitCntrLockTrans( so_no )                  
    return 0
end
 
return wms_out