1
Jianw
2025-07-09 f6f5e6b632d6649386a380558d84003f3de7ec6c
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
 --[[
    编码: JX-API-06
    名称: 出库单以及明细新增
    作者: kun
    入口函数:CreateOutbound
    日期: 2025-4-16
 
    功能:
        - 根据物料 area_code 对 ITEMS 进行分组,每组创建一张出库单。
        
            输入数据格式:
           {
            "Name": "GetOutboundOrder",
            "Source": "ERP",
            "Data": {
                    "S_BS_NO": "CKD001",
                    "S_OP_TYPE": "采购退货",
                    "S_OUT_TO": "生产部",
                    "S_SHIFT_CODE": "",
                    "S_PRODUCT_TYPE": "5100",
                    "F_DIMENSION": 80,
                    "S_ITEM_DESC": "",
                    "S_SN": "",
                    "S_WO_NO": "",
                    "N_PRIORITY": 1,
                    "S_NOTE": "",
                    "ITEMS": [
                        {
                        "S_BS_NO": "11",
                        "N_BS_ROW_MO": 11,
                        "S_ITEM_CODE": "CS001",
                        "S_ITEM_STATE": "O",
                        "S_SUPPLIER_NO":"1",
                        "S_SUPPLIER_NAME":"2",
                        "F_QTY": 10,
                        "S_EXT_ATTR1":"",
                       "S_EXT_ATTR2":"2",
                       "S_EXT_ATTR3":"",
                       "S_EXT_ATTR4":"1",
                      "S_EXT_ATTR5":""
                        },
                        {
                        "S_BS_NO": "21",
                        "N_BS_ROW_MO": 21,
                        "S_ITEM_CODE": "A234",
                        "S_ITEM_STATE": "O",
                        "S_SUPPLIER_NO":"",
                        "S_SUPPLIER_NAME":"",
                        "F_QTY": 10,
                        "S_EXT_ATTR1":"2",
                       "S_EXT_ATTR2":"1",
                       "S_EXT_ATTR3":"2",
                       "S_EXT_ATTR4":"",
                      "S_EXT_ATTR5":""
                        },
                        {
                        "S_BS_NO": "31",
                        "N_BS_ROW_MO": 31,
                        "S_ITEM_CODE": "A234",
                        "S_ITEM_STATE": "O",
                        "S_SUPPLIER_NO":"",
                        "S_SUPPLIER_NAME":"",
                        "F_QTY": 10,
                        "S_EXT_ATTR1":"",
                       "S_EXT_ATTR2":"",
                       "S_EXT_ATTR3":"",
                       "S_EXT_ATTR4":"21",
                      "S_EXT_ATTR5":"1"
                        }
                        ]
                }
            }
--]]
 
json = require("json")
mobox = require("OILua_JavelinExt")
m3 = require("oi_base_mobox")
wms_base = require("wms_base")
 
 function CreateOutbound(strLuaDEID) 
    local nRet, inputData
    local SqlCondition
    local nRetl, strRetInfo
    local strCondition
    local num
    local area_groups = {}
    local err = {}
    local result_code_list = {}
    
    local seen_combinations = {}
    local duplicate_found = false
    local duplicate_items = {}
    
 
    -- 获取接口传入数据
    nRet, inputData = m3.GetSysDataJson(strLuaDEID)
    if (nRet ~= 0) then
        table.insert(err, "无法获取数据包!" )
        goto err_msg_process 
    end
 
    if not inputData or not inputData.ITEMS or #inputData.ITEMS == 0 then
        table.insert(err, "Data 或 ITEMS 不合法!" )
        goto err_msg_process 
    end
 
    -- 校验出库类型
    if (inputData.S_OP_TYPE == nil or inputData.S_OP_TYPE == "") then
        table.insert(err, "业务来源S_OP_TYPE不能为空或不合法!" )
        goto err_msg_process 
    end
    
    -- 计算发货箱数量
    if  ( inputData.S_PRODUCT_TYPE ~= "" and inputData.F_DIMENSION ~= "") or (not inputData.S_PRODUCT_TYPE and not inputData.F_DIMENSION) then
        SqlCondition = "S_PRODUCT_TYPE = '" ..inputData.S_PRODUCT_TYPE.. "' and F_DIMENSION = " .. inputData.F_DIMENSION
        nRetl, strRetInfo = mobox.existThisData(strLuaDEID, "ShipmentBox", SqlCondition)
        if (nRetl ~= 0) then
            table.insert(err, "调用方法 existThisData 出错" .. inputData.S_BS_NO )
            goto err_msg_process 
        end
        if (strRetInfo ~= 'yes') then
            table.insert(err, "发货箱无该类型数据,请补充!!!" .. inputData.S_BS_NO )
            goto err_msg_process 
        end
        nRet, strRetInfo = m3.GetDataObjByCondition(strLuaDEID, "ShipmentBox", SqlCondition)
        if (nRet ~= 0) then 
            table.insert(err, "查询发货箱表失败" .. inputData.S_BS_NO )
            goto err_msg_process 
        end
        num = strRetInfo.qty
    end
 
    -- 校验来源单号
    if (inputData.S_BS_NO == nil or inputData.S_BS_NO == "") then
        table.insert(err, "业务来源S_BS_NO不能为空或不合法" )
        goto err_msg_process 
    end
    strCondition = " S_BS_NO = '" ..inputData.S_BS_NO.. "' "
    nRetl, strRetInfo = mobox.existThisData(strLuaDEID, "Outbound_Order", strCondition)
    if (nRetl ~= 0) then
        table.insert(err, "调用方法 existThisData 出错" .. inputData.S_BS_NO )
        goto err_msg_process 
    end
    if (strRetInfo == 'yes') then
        table.insert(err, "来源单号存在" .. inputData.S_BS_NO )
        goto err_msg_process 
    end
    
    nRet,wh_code = wms_base.Get_sConst2("默认工厂标识" )
    if ( nRet ~= 0 ) then
        table.insert(errcode, "系统常量:默认工厂标识" .. inputData.S_BS_NO)
        goto err_msg_process
    end
    if ( wh_code == '' ) then
        table.insert(errcode, "系统常量'默认工厂标识'必须有值!" .. inputData.S_BS_NO)
        goto err_msg_process
    end
        nRet,storer = wms_base.Get_sConst2( "WMS_Default_Storer" )
    if ( nRet ~= 0 ) then
        table.insert(errcode, "系统常量:WMS_Default_Storer" .. inputData.S_BS_NO)
        goto err_msg_process
    end
    if ( storer == '' ) then
        table.insert(errcode, "系统常量'WMS_Default_Storer'必须有值!" .. inputData.S_BS_NO)
        goto err_msg_process
    end
    
    -- 新增校验:检查重复的S_BS_NO和N_BS_ROW_MO组合
 
    for _, item in ipairs(inputData.ITEMS) do
        local combination_key = item.S_BS_NO .. "|" .. tostring(item.N_BS_ROW_MO)
        
        if seen_combinations[combination_key] then
            duplicate_found = true
            table.insert(duplicate_items, combination_key)
        else
            seen_combinations[combination_key] = true
        end
    end
    
    if duplicate_found then
        table.insert(err, "存在重复的来源单号和行号组合: " .. table.concat(duplicate_items, ", "))
        goto err_msg_process
    end
 
    -- 按 area_code 分组
    for _, item in ipairs(inputData.ITEMS) do
        if not item.S_ITEM_CODE or item.S_ITEM_CODE == "" then
            table.insert(err, "商品编码不能为空" ..item.S_ITEM_CODE)
        elseif not item.F_QTY or item.F_QTY == 0 then
            table.insert(err, "商品数量不能为0" ..item.S_ITEM_CODE)
        elseif not item.S_BS_NO or  item.S_BS_NO == "" then
            table.insert(err, "来源单号不能为空" ..item.S_ITEM_CODE)
        elseif not item.N_BS_ROW_MO or  item.N_BS_ROW_MO == "" then
            table.insert(err, "来源行号不能为空" ..item.S_ITEM_CODE)
        elseif (type(item.N_BS_ROW_MO) ~= "number") then
            table.insert(err, "来源行号非数字类型" ..item.S_ITEM_CODE)
        else
            local strCondition = "S_BS_NO = '" ..item.S_BS_NO.. "' and N_BS_ROW_MO = '" ..item.N_BS_ROW_MO.. "'" --  N_BS_ROW_MO = '" ..item.N_BS_ROW_MO.. "' and 
            local nRetl, strRetInfo = mobox.existThisData(strLuaDEID, "Outbound_Detail", strCondition)
            if (nRetl ~= 0) then
                table.insert(err, "调用方法 existThisData 出错: " .. strRetInfo)
            end
            if (strRetInfo == 'yes') then
                table.insert(err, "该物料的来源单行号已存在:" .. item.S_ITEM_CODE)
            end
            
            -- 查询物料,获取 area_code
            local cond = "S_ITEM_CODE = '" .. item.S_ITEM_CODE .. "'"
            local ret1, id, materialAttrs = mobox.getDataObjAttrByKeyAttr(strLuaDEID, "SKU", cond)
            if ret1 ~= 0 or not materialAttrs then
                table.insert(err, "获取物料失败:" .. item.S_ITEM_CODE)
            else
                local ret2, materialJson = mobox.objAttrsToLuaJson("SKU", materialAttrs)
                if ret2 ~= 0 then
                    table.insert(err, "物料JSON转换失败:" .. item.S_ITEM_CODE)
                else
                    local ok, material = pcall(json.decode, materialJson)
                    if not ok or not material or not material.udf01 then
                        table.insert(err, "物料无有效area_code:" .. item.S_ITEM_CODE)
                    else
                        item._material = material
                        local area = material.udf01
                        if not area_groups[area] then
                            area_groups[area] = {}
                        end
                        table.insert(area_groups[area], item)
                    end
                end
            end
        end
    end
    
    ::err_msg_process::
    -- 如校验失败,返回错误结构
    if #err > 0 then
        local result = {
            SourceKey = inputData.SourceKey ,
            err_code = 1,
            err_msg = table.concat(err, ","),
            result = nil
        }
        mobox.returnValue(strLuaDEID, 1, lua.table2str(result))
        return
    end
 
    -- 创建入库单(按 area_code 拆分)
    for area_code, items in pairs(area_groups) do
        local header = 'CK' .. os.date("%y%m%d") .. '-'
        local ret, order_no = mobox.getSerialNumber("出库单", header, 4)
        if ret ~= 0 then
            table.insert(err, "申请入库单编码失败:" .. order_no)
            goto continue
        end
 
        local order = m3.AllocObject(strLuaDEID, "Outbound_Order")
        order.state = "发布"                                                -- 单据状态                                                     
        order.no = order_no                                                 -- 出库单号     
        order.out_type = inputData.S_OP_TYPE                                -- 出库类型
        order.bs_no = inputData.S_BS_NO                                     -- 来源单号 
        order.note = inputData.S_NOTE                                       -- 备注                 
        order.out_to = inputData.S_OUT_TO                                   -- 出库方向
        order.shift_code = inputData.S_SHIFT_CODE                           -- 班次编码
        order.product_type = inputData.S_PRODUCT_TYPE                       -- 产品类型
        order.dimension = inputData.F_DIMENSION                             -- 尺寸
        order.priority = inputData.N_PRIORITY                             -- 优先级
        order.sn = inputData.S_SN                                           -- 序列号
        order.wo_no = inputData.S_WO_NO                                     -- 工单号
        order.box_qty = num                                                 -- 发货箱数量
 
        order.sour_no = inputData.SourceKey 
        order.wh_code = wh_code
        order.area_code = area_code                                         -- 库区编码       
        
 
 
        for _, item in ipairs(items) do
            local material = item._material
            local detail = m3.AllocObject(strLuaDEID, "Outbound_Detail")
            detail.oo_no = order_no
            detail.qty = item.F_QTY
            detail.bs_no = item.S_BS_NO
            detail.bs_row_no = item.N_BS_ROW_MO
            detail.item_state = item.S_ITEM_STATE
            detail.supplier = item.S_SUPPLIER_NO
            detail.supplier_name = item.S_SUPPLIER_NAME
 
            detail.item_code = material.item_code
            detail.item_name = material.item_name
            
            detail.sour_no = inputData.SourceKey 
            detail.storer = storer
            
            detail.ext_attr1 = item.S_EXT_ATTR1
            detail.ext_attr2 = item.S_EXT_ATTR2
            detail.ext_attr3 = item.S_EXT_ATTR3
            detail.ext_attr4 = item.S_EXT_ATTR4
            detail.ext_attr5 = item.S_EXT_ATTR5
            
 
 
            local ret, msg = m3.CreateDataObj(strLuaDEID, detail)
            if ret ~= 0 then
                lua.Stop(strLuaDEID, "创建入库单明细失败:" .. msg)
                return
            end
 
        end
 
        local ret3, msg3 = m3.CreateDataObj(strLuaDEID, order)
        if ret3 ~= 0 then
            lua.Stop(strLuaDEID, "创建入库单明细失败:" .. msg3)
            return
        end
 
        table.insert(result_code_list, order_no)
 
        ::continue::
    end
 
    -- 统一格式返回结果
    local final_result
    if #err > 0 then
        final_result = {
            SourceKey = inputData.SourceKey or "",
            err_code = 1,
            err_msg = "出库单创建异常:" .. table.concat(err, ","),
            result = nil
        }
    else
        final_result = {
            SourceKey = inputData.SourceKey or "",
            err_code = 0,
            err_msg = "出库单创建成功!",
            result = {
                S_NO = inputData.S_BS_NO
            }
        }
    end
 
    mobox.returnValue(strLuaDEID, 1, lua.table2str(final_result))
end